mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[eem] rename asset_manager to entity_manager (#186617)
## Summary Renames the experimental asset_manager plugin (never documented/officially released) into entity_manager. I've used `node scripts/lint_ts_projects --fix` and `node scripts/lint_packages.js --fix` to help with the procedure and also renamed manually the asset_manager references left. The change also removes the deprecated asset_manager code, including the `assetManager.alphaEnabled` plugin configuration. This means entityManager plugin will be enabled by default. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
34f76adc75
commit
a493e4075b
152 changed files with 302 additions and 4793 deletions
|
@ -193,7 +193,6 @@ enabled:
|
|||
- x-pack/test/api_integration/config_security_basic.ts
|
||||
- x-pack/test/api_integration/config_security_trial.ts
|
||||
- x-pack/test/api_integration/apis/aiops/config.ts
|
||||
- x-pack/test/api_integration/apis/asset_manager/config_when_disabled.ts
|
||||
- x-pack/test/api_integration/apis/cases/config.ts
|
||||
- x-pack/test/api_integration/apis/content_management/config.ts
|
||||
- x-pack/test/api_integration/apis/cloud_security_posture/config.ts
|
||||
|
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -47,7 +47,6 @@ packages/kbn-apm-synthtrace-client @elastic/obs-ux-infra_services-team @elastic/
|
|||
packages/kbn-apm-utils @elastic/obs-ux-infra_services-team
|
||||
test/plugin_functional/plugins/app_link_test @elastic/kibana-core
|
||||
x-pack/test/usage_collection/plugins/application_usage_test @elastic/kibana-core
|
||||
x-pack/plugins/observability_solution/asset_manager @elastic/obs-knowledge-team
|
||||
x-pack/plugins/observability_solution/assets_data_access @elastic/obs-knowledge-team
|
||||
x-pack/test/security_api_integration/plugins/audit_log @elastic/kibana-security
|
||||
packages/kbn-axe-config @elastic/kibana-qa
|
||||
|
@ -391,6 +390,7 @@ x-pack/examples/embedded_lens_example @elastic/kibana-visualizations
|
|||
x-pack/plugins/encrypted_saved_objects @elastic/kibana-security
|
||||
x-pack/plugins/enterprise_search @elastic/search-kibana
|
||||
x-pack/packages/kbn-entities-schema @elastic/obs-knowledge-team
|
||||
x-pack/plugins/observability_solution/entity_manager @elastic/obs-knowledge-team
|
||||
examples/error_boundary @elastic/appex-sharedux
|
||||
packages/kbn-es @elastic/kibana-operations
|
||||
packages/kbn-es-archiver @elastic/kibana-operations @elastic/appex-qa
|
||||
|
|
|
@ -1,193 +0,0 @@
|
|||
{
|
||||
"id": "assetManager",
|
||||
"client": {
|
||||
"classes": [],
|
||||
"functions": [],
|
||||
"interfaces": [
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerPublicPluginSetup",
|
||||
"type": "Interface",
|
||||
"tags": [],
|
||||
"label": "AssetManagerPublicPluginSetup",
|
||||
"description": [],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/types.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"children": [
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerPublicPluginSetup.publicAssetsClient",
|
||||
"type": "Object",
|
||||
"tags": [],
|
||||
"label": "publicAssetsClient",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"IPublicAssetsClient"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/types.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerPublicPluginSetup.entityClient",
|
||||
"type": "Object",
|
||||
"tags": [],
|
||||
"label": "entityClient",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"IEntityClient"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/types.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false
|
||||
}
|
||||
],
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerPublicPluginStart",
|
||||
"type": "Interface",
|
||||
"tags": [],
|
||||
"label": "AssetManagerPublicPluginStart",
|
||||
"description": [],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/types.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"children": [
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerPublicPluginStart.publicAssetsClient",
|
||||
"type": "Object",
|
||||
"tags": [],
|
||||
"label": "publicAssetsClient",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"IPublicAssetsClient"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/types.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerPublicPluginStart.entityClient",
|
||||
"type": "Object",
|
||||
"tags": [],
|
||||
"label": "entityClient",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"IEntityClient"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/types.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false
|
||||
}
|
||||
],
|
||||
"initialIsOpen": false
|
||||
}
|
||||
],
|
||||
"enums": [],
|
||||
"misc": [
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-public.AssetManagerAppId",
|
||||
"type": "Type",
|
||||
"tags": [],
|
||||
"label": "AssetManagerAppId",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"\"assetManager\""
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/public/index.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"initialIsOpen": false
|
||||
}
|
||||
],
|
||||
"objects": []
|
||||
},
|
||||
"server": {
|
||||
"classes": [],
|
||||
"functions": [],
|
||||
"interfaces": [],
|
||||
"enums": [],
|
||||
"misc": [
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-server.AssetManagerConfig",
|
||||
"type": "Type",
|
||||
"tags": [],
|
||||
"label": "AssetManagerConfig",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"{ readonly alphaEnabled?: boolean | undefined; readonly sourceIndices: Readonly<{} & { logs: string; }>; }"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/common/config.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"initialIsOpen": false
|
||||
},
|
||||
{
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-server.WriteSamplesPostBody",
|
||||
"type": "Type",
|
||||
"tags": [],
|
||||
"label": "WriteSamplesPostBody",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"{ baseDateTime?: string | number | undefined; excludeEans?: string[] | undefined; refresh?: boolean | \"wait_for\" | undefined; } | null"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/sample_assets.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"initialIsOpen": false
|
||||
}
|
||||
],
|
||||
"objects": [],
|
||||
"setup": {
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-server.AssetManagerServerPluginSetup",
|
||||
"type": "Type",
|
||||
"tags": [],
|
||||
"label": "AssetManagerServerPluginSetup",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"{ assetClient: ",
|
||||
"AssetClient",
|
||||
"; } | undefined"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/plugin.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"lifecycle": "setup",
|
||||
"initialIsOpen": true
|
||||
},
|
||||
"start": {
|
||||
"parentPluginId": "assetManager",
|
||||
"id": "def-server.AssetManagerServerPluginStart",
|
||||
"type": "Type",
|
||||
"tags": [],
|
||||
"label": "AssetManagerServerPluginStart",
|
||||
"description": [],
|
||||
"signature": [
|
||||
"{} | undefined"
|
||||
],
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/plugin.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"lifecycle": "start",
|
||||
"initialIsOpen": true
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"classes": [],
|
||||
"functions": [],
|
||||
"interfaces": [],
|
||||
"enums": [],
|
||||
"misc": [],
|
||||
"objects": []
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
---
|
||||
####
|
||||
#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system.
|
||||
#### Reach out in #docs-engineering for more info.
|
||||
####
|
||||
id: kibAssetManagerPluginApi
|
||||
slug: /kibana-dev-docs/api/assetManager
|
||||
title: "assetManager"
|
||||
image: https://source.unsplash.com/400x175/?github
|
||||
description: API docs for the assetManager plugin
|
||||
date: 2024-06-26
|
||||
tags: ['contributor', 'dev', 'apidocs', 'kibana', 'assetManager']
|
||||
---
|
||||
import assetManagerObj from './asset_manager.devdocs.json';
|
||||
|
||||
Asset manager plugin for entity assets (inventory, topology, etc)
|
||||
|
||||
Contact [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) for questions regarding this plugin.
|
||||
|
||||
**Code health stats**
|
||||
|
||||
| Public API count | Any count | Items lacking comments | Missing exports |
|
||||
|-------------------|-----------|------------------------|-----------------|
|
||||
| 11 | 0 | 11 | 3 |
|
||||
|
||||
## Client
|
||||
|
||||
### Interfaces
|
||||
<DocDefinitionList data={assetManagerObj.client.interfaces}/>
|
||||
|
||||
### Consts, variables and types
|
||||
<DocDefinitionList data={assetManagerObj.client.misc}/>
|
||||
|
||||
## Server
|
||||
|
||||
### Setup
|
||||
<DocDefinitionList data={[assetManagerObj.server.setup]}/>
|
||||
|
||||
### Start
|
||||
<DocDefinitionList data={[assetManagerObj.server.start]}/>
|
||||
|
||||
### Consts, variables and types
|
||||
<DocDefinitionList data={assetManagerObj.server.misc}/>
|
||||
|
|
@ -39,7 +39,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana']
|
|||
| <DocLink id="kibDataPluginApi" section="def-common.EqlSearchStrategyRequest.options" text="options"/> | securitySolution | - |
|
||||
| <DocLink id="kibFleetPluginApi" section="def-public.NewPackagePolicy.policy_id" text="policy_id"/> | cloudDefend, osquery, securitySolution, synthetics | - |
|
||||
| <DocLink id="kibFleetPluginApi" section="def-common.NewPackagePolicy.policy_id" text="policy_id"/> | cloudDefend, osquery, securitySolution, synthetics | - |
|
||||
| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | actions, alerting, files, cases, observabilityAIAssistant, fleet, cloudDefend, cloudSecurityPosture, elasticAssistant, enterpriseSearch, lists, osquery, securitySolution, reporting, serverlessSearch, transform, upgradeAssistant, apm, assetManager, observabilityOnboarding, synthetics, security | - |
|
||||
| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | actions, alerting, files, cases, observabilityAIAssistant, fleet, cloudDefend, cloudSecurityPosture, elasticAssistant, enterpriseSearch, lists, osquery, securitySolution, reporting, serverlessSearch, transform, upgradeAssistant, apm, entityManager, observabilityOnboarding, synthetics, security | - |
|
||||
| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.userProfiles" text="userProfiles"/> | cases, securitySolution, security | - |
|
||||
| <DocLink id="kibTimelinesPluginApi" section="def-common.DeprecatedCellValueElementProps" text="DeprecatedCellValueElementProps"/> | @kbn/securitysolution-data-table, securitySolution | - |
|
||||
| <DocLink id="kibTimelinesPluginApi" section="def-common.DeprecatedRowRenderer" text="DeprecatedRowRenderer"/> | @kbn/securitysolution-data-table, securitySolution | - |
|
||||
|
@ -240,4 +240,4 @@ Safe to remove.
|
|||
| <DocLink id="kibKbnCoreSavedObjectsApiBrowserPluginApi" section="def-common.SavedObjectsBulkDeleteResponseItem" text="SavedObjectsBulkDeleteResponseItem"/> | @kbn/core-saved-objects-api-browser |
|
||||
| <DocLink id="kibKbnStorybookPluginApi" section="def-common.StorybookConfig.config" text="config"/> | @kbn/storybook |
|
||||
| <DocLink id="kibKbnUiThemePluginApi" section="def-common.tag" text="tag"/> | @kbn/ui-theme |
|
||||
| <DocLink id="kibKbnUiThemePluginApi" section="def-common.version" text="version"/> | @kbn/ui-theme |
|
||||
| <DocLink id="kibKbnUiThemePluginApi" section="def-common.version" text="version"/> | @kbn/ui-theme |
|
||||
|
|
|
@ -511,14 +511,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana']
|
|||
|
||||
|
||||
|
||||
## assetManager
|
||||
|
||||
| Deprecated API | Reference location(s) | Remove By |
|
||||
| ---------------|-----------|-----------|
|
||||
| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [enable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/enable.ts#:~:text=authc), [disable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/disable.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [enable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/enable.ts#:~:text=authc), [disable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/disable.ts#:~:text=authc) | - |
|
||||
|
||||
|
||||
|
||||
## canvas
|
||||
|
||||
| Deprecated API | Reference location(s) | Remove By |
|
||||
|
@ -785,6 +777,14 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana']
|
|||
|
||||
|
||||
|
||||
## entityManager
|
||||
|
||||
| Deprecated API | Reference location(s) | Remove By |
|
||||
| ---------------|-----------|-----------|
|
||||
| <DocLink id="kibSecurityPluginApi" section="def-server.SecurityPluginStart.authc" text="authc"/> | [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [enable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts#:~:text=authc), [disable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [api_key.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts#:~:text=authc), [enable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts#:~:text=authc), [disable.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts#:~:text=authc) | - |
|
||||
|
||||
|
||||
|
||||
## eventAnnotation
|
||||
|
||||
| Deprecated API | Reference location(s) | Remove By |
|
||||
|
@ -1643,4 +1643,4 @@ migrates to using the Kibana Privilege model: https://github.com/elastic/kibana/
|
|||
|
||||
| Deprecated API | Reference location(s) | Remove By |
|
||||
| ---------------|-----------|-----------|
|
||||
| <DocLink id="kibLicensingPluginApi" section="def-public.LicensingPluginSetup.license$" text="license$"/> | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24) | 8.8.0 |
|
||||
| <DocLink id="kibLicensingPluginApi" section="def-public.LicensingPluginSetup.license$" text="license$"/> | [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24), [plugin.ts](https://github.com/elastic/kibana/tree/main/x-pack/plugins/watcher/public/plugin.ts#:~:text=license%24) | 8.8.0 |
|
||||
|
|
|
@ -4730,42 +4730,6 @@
|
|||
"plugin": "grokdebugger",
|
||||
"path": "x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/ping.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/sample_assets.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/assets/index.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/assets/hosts.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/assets/services.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/assets/containers.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/assets/pods.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/entities/get.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/check.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "profiling",
|
||||
"path": "x-pack/plugins/observability_solution/profiling/server/routes/apm.ts"
|
||||
|
@ -7248,18 +7212,6 @@
|
|||
"plugin": "grokdebugger",
|
||||
"path": "x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/sample_assets.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/entities/create.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/entities/reset.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "profiling",
|
||||
"path": "x-pack/plugins/observability_solution/profiling/server/routes/setup/route.ts"
|
||||
|
@ -8758,10 +8710,6 @@
|
|||
"plugin": "grokdebugger",
|
||||
"path": "x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/enable.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "synthetics",
|
||||
"path": "x-pack/plugins/observability_solution/synthetics/server/server.ts"
|
||||
|
@ -9682,18 +9630,6 @@
|
|||
"plugin": "grokdebugger",
|
||||
"path": "x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/sample_assets.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/entities/delete.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/disable.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "synthetics",
|
||||
"path": "x-pack/plugins/observability_solution/synthetics/server/server.ts"
|
||||
|
@ -19735,4 +19671,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3469,24 +3469,24 @@
|
|||
"path": "x-pack/plugins/observability_solution/apm/server/routes/fleet/is_superuser.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/enable.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/disable.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "observabilityOnboarding",
|
||||
|
@ -4961,4 +4961,4 @@
|
|||
"misc": [],
|
||||
"objects": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana']
|
|||
| <DocLink id="kibAlertingPluginApi" text="alerting"/> | [@elastic/response-ops](https://github.com/orgs/elastic/teams/response-ops) | - | 868 | 1 | 836 | 52 |
|
||||
| <DocLink id="kibApmPluginApi" text="apm"/> | [@elastic/obs-ux-infra_services-team](https://github.com/orgs/elastic/teams/obs-ux-infra_services-team) | The user interface for Elastic APM | 29 | 0 | 29 | 123 |
|
||||
| <DocLink id="kibApmDataAccessPluginApi" text="apmDataAccess"/> | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 9 | 0 | 9 | 0 |
|
||||
| <DocLink id="kibAssetManagerPluginApi" text="assetManager"/> | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | Asset manager plugin for entity assets (inventory, topology, etc) | 11 | 0 | 11 | 3 |
|
||||
| <DocLink id="kibAssetsDataAccessPluginApi" text="assetsDataAccess"/> | [@elastic/obs-knowledge-team](https://github.com/orgs/elastic/teams/obs-knowledge-team) | - | 2 | 0 | 2 | 0 |
|
||||
| <DocLink id="kibBannersPluginApi" text="banners"/> | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | - | 9 | 0 | 9 | 0 |
|
||||
| <DocLink id="kibBfetchPluginApi" text="bfetch"/> | [@elastic/appex-sharedux](https://github.com/orgs/elastic/teams/appex-sharedux) | Considering using bfetch capabilities when fetching large amounts of data. This services supports batching HTTP requests and streaming responses back. | 83 | 1 | 73 | 2 |
|
||||
|
|
|
@ -5675,24 +5675,24 @@
|
|||
"path": "x-pack/plugins/observability_solution/apm/server/routes/fleet/is_superuser.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/lib/auth/api_key/api_key.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/lib/auth/api_key/api_key.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/enable.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "assetManager",
|
||||
"path": "x-pack/plugins/observability_solution/asset_manager/server/routes/enablement/disable.ts"
|
||||
"plugin": "entityManager",
|
||||
"path": "x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/disable.ts"
|
||||
},
|
||||
{
|
||||
"plugin": "observabilityOnboarding",
|
||||
|
@ -8021,4 +8021,4 @@
|
|||
],
|
||||
"objects": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -470,10 +470,6 @@ The plugin exposes the static DefaultEditorController class to consume.
|
|||
|WARNING: Missing README.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/asset_manager/README.md[assetManager]
|
||||
|This plugin provides access to observed asset data, such as information about hosts, pods, containers, services, and more.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/assets_data_access[assetsDataAccess]
|
||||
|WARNING: Missing README.
|
||||
|
||||
|
@ -573,6 +569,10 @@ security and spaces filtering.
|
|||
|This plugin provides Kibana user interfaces for managing the Enterprise Search solution and its products, App Search and Workplace Search.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/entity_manager/README.md[entityManager]
|
||||
|This plugin provides access to observed asset data, such as information about hosts, pods, containers, services, and more.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/event_log/README.md[eventLog]
|
||||
|The event log plugin provides a persistent history of alerting and action
|
||||
activities.
|
||||
|
|
|
@ -176,7 +176,6 @@
|
|||
"@kbn/apm-utils": "link:packages/kbn-apm-utils",
|
||||
"@kbn/app-link-test-plugin": "link:test/plugin_functional/plugins/app_link_test",
|
||||
"@kbn/application-usage-test-plugin": "link:x-pack/test/usage_collection/plugins/application_usage_test",
|
||||
"@kbn/assetManager-plugin": "link:x-pack/plugins/observability_solution/asset_manager",
|
||||
"@kbn/assets-data-access-plugin": "link:x-pack/plugins/observability_solution/assets_data_access",
|
||||
"@kbn/audit-log-plugin": "link:x-pack/test/security_api_integration/plugins/audit_log",
|
||||
"@kbn/banners-plugin": "link:x-pack/plugins/banners",
|
||||
|
@ -446,6 +445,7 @@
|
|||
"@kbn/encrypted-saved-objects-plugin": "link:x-pack/plugins/encrypted_saved_objects",
|
||||
"@kbn/enterprise-search-plugin": "link:x-pack/plugins/enterprise_search",
|
||||
"@kbn/entities-schema": "link:x-pack/packages/kbn-entities-schema",
|
||||
"@kbn/entityManager-plugin": "link:x-pack/plugins/observability_solution/entity_manager",
|
||||
"@kbn/error-boundary-example-plugin": "link:examples/error_boundary",
|
||||
"@kbn/es-errors": "link:packages/kbn-es-errors",
|
||||
"@kbn/es-query": "link:packages/kbn-es-query",
|
||||
|
|
|
@ -290,6 +290,22 @@
|
|||
"schemaVersion"
|
||||
],
|
||||
"enterprise_search_telemetry": [],
|
||||
"entity-definition": [
|
||||
"description",
|
||||
"filter",
|
||||
"id",
|
||||
"identityFields",
|
||||
"indexPatterns",
|
||||
"managed",
|
||||
"metadata",
|
||||
"metrics",
|
||||
"name",
|
||||
"staticFields",
|
||||
"type"
|
||||
],
|
||||
"entity-discovery-api-key": [
|
||||
"apiKey"
|
||||
],
|
||||
"epm-packages": [
|
||||
"es_index_patterns",
|
||||
"experimental_data_stream_features",
|
||||
|
|
|
@ -993,6 +993,52 @@
|
|||
"dynamic": false,
|
||||
"properties": {}
|
||||
},
|
||||
"entity-definition": {
|
||||
"dynamic": false,
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"filter": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"identityFields": {
|
||||
"type": "object"
|
||||
},
|
||||
"indexPatterns": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"managed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object"
|
||||
},
|
||||
"metrics": {
|
||||
"type": "object"
|
||||
},
|
||||
"name": {
|
||||
"type": "text"
|
||||
},
|
||||
"staticFields": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"entity-discovery-api-key": {
|
||||
"dynamic": false,
|
||||
"properties": {
|
||||
"apiKey": {
|
||||
"type": "binary"
|
||||
}
|
||||
}
|
||||
},
|
||||
"epm-packages": {
|
||||
"properties": {
|
||||
"es_index_patterns": {
|
||||
|
|
|
@ -5,7 +5,6 @@ pageLoadAssetSize:
|
|||
aiops: 10000
|
||||
alerting: 106936
|
||||
apm: 64385
|
||||
assetManager: 25000
|
||||
banners: 17946
|
||||
bfetch: 22837
|
||||
canvas: 29355
|
||||
|
@ -42,6 +41,7 @@ pageLoadAssetSize:
|
|||
embeddable: 87309
|
||||
embeddableEnhanced: 22107
|
||||
enterpriseSearch: 66810
|
||||
entityManager: 17175
|
||||
esqlDataGrid: 24582
|
||||
esUiShared: 326654
|
||||
eventAnnotation: 30000
|
||||
|
|
|
@ -88,6 +88,8 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"endpoint:unified-user-artifact-manifest": "71c7fcb52c658b21ea2800a6b6a76972ae1c776e",
|
||||
"endpoint:user-artifact-manifest": "1c3533161811a58772e30cdc77bac4631da3ef2b",
|
||||
"enterprise_search_telemetry": "9ac912e1417fc8681e0cd383775382117c9e3d3d",
|
||||
"entity-definition": "33fe0194bd896f0bfe479d55f6de20f8ba1d7713",
|
||||
"entity-discovery-api-key": "c267a65c69171d1804362155c1378365f5acef88",
|
||||
"epm-packages": "f8ee125b57df31fd035dc04ad81aef475fd2f5bd",
|
||||
"epm-packages-assets": "7a3e58efd9a14191d0d1a00b8aaed30a145fd0b1",
|
||||
"event-annotation-group": "715ba867d8c68f3c9438052210ea1c30a9362582",
|
||||
|
|
|
@ -51,6 +51,8 @@ const previouslyRegisteredTypes = [
|
|||
'endpoint:user-artifact-manifest',
|
||||
'endpoint:unified-user-artifact-manifest',
|
||||
'enterprise_search_telemetry',
|
||||
'entity-definition',
|
||||
'entity-discovery-api-key',
|
||||
'epm-packages',
|
||||
'epm-packages-assets',
|
||||
'event_loop_delays_daily',
|
||||
|
|
|
@ -208,6 +208,8 @@ describe('split .kibana index into multiple system indices', () => {
|
|||
"endpoint:unified-user-artifact-manifest",
|
||||
"endpoint:user-artifact-manifest",
|
||||
"enterprise_search_telemetry",
|
||||
"entity-definition",
|
||||
"entity-discovery-api-key",
|
||||
"epm-packages",
|
||||
"epm-packages-assets",
|
||||
"event-annotation-group",
|
||||
|
|
|
@ -220,7 +220,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.apm.featureFlags.storageExplorerAvailable (any)',
|
||||
'xpack.apm.featureFlags.profilingIntegrationAvailable (boolean)',
|
||||
'xpack.apm.serverless.enabled (any)', // It's a boolean (any because schema.conditional)
|
||||
'xpack.assetManager.alphaEnabled (boolean)',
|
||||
'xpack.observability_onboarding.serverless.enabled (any)', // It's a boolean (any because schema.conditional)
|
||||
'xpack.cases.files.allowedMimeTypes (array)',
|
||||
'xpack.cases.files.maxSize (number)',
|
||||
|
|
|
@ -88,8 +88,6 @@
|
|||
"@kbn/app-link-test-plugin/*": ["test/plugin_functional/plugins/app_link_test/*"],
|
||||
"@kbn/application-usage-test-plugin": ["x-pack/test/usage_collection/plugins/application_usage_test"],
|
||||
"@kbn/application-usage-test-plugin/*": ["x-pack/test/usage_collection/plugins/application_usage_test/*"],
|
||||
"@kbn/assetManager-plugin": ["x-pack/plugins/observability_solution/asset_manager"],
|
||||
"@kbn/assetManager-plugin/*": ["x-pack/plugins/observability_solution/asset_manager/*"],
|
||||
"@kbn/assets-data-access-plugin": ["x-pack/plugins/observability_solution/assets_data_access"],
|
||||
"@kbn/assets-data-access-plugin/*": ["x-pack/plugins/observability_solution/assets_data_access/*"],
|
||||
"@kbn/audit-log-plugin": ["x-pack/test/security_api_integration/plugins/audit_log"],
|
||||
|
@ -776,6 +774,8 @@
|
|||
"@kbn/enterprise-search-plugin/*": ["x-pack/plugins/enterprise_search/*"],
|
||||
"@kbn/entities-schema": ["x-pack/packages/kbn-entities-schema"],
|
||||
"@kbn/entities-schema/*": ["x-pack/packages/kbn-entities-schema/*"],
|
||||
"@kbn/entityManager-plugin": ["x-pack/plugins/observability_solution/entity_manager"],
|
||||
"@kbn/entityManager-plugin/*": ["x-pack/plugins/observability_solution/entity_manager/*"],
|
||||
"@kbn/error-boundary-example-plugin": ["examples/error_boundary"],
|
||||
"@kbn/error-boundary-example-plugin/*": ["examples/error_boundary/*"],
|
||||
"@kbn/es": ["packages/kbn-es"],
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# Asset Manager Plugin
|
||||
|
||||
This plugin provides access to observed asset data, such as information about hosts, pods, containers, services, and more.
|
||||
|
||||
## Documentation
|
||||
|
||||
### User Docs
|
||||
|
||||
For those interested in making use of the APIs provided by this plugin, see [our API docs](./docs/api.md).
|
||||
|
||||
### Developer Docs
|
||||
|
||||
For those working on this plugin directly and developing it, please see [our development docs](./docs/development.md).
|
|
@ -1,48 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
export const INDEX_DEFAULTS = {
|
||||
logs: 'filebeat-*,logs-*',
|
||||
};
|
||||
|
||||
export const configSchema = schema.object({
|
||||
alphaEnabled: schema.maybe(schema.boolean()),
|
||||
// Designate where various types of data live.
|
||||
// NOTE: this should be handled in a centralized way for observability, so
|
||||
// that when a user configures these differently from the known defaults,
|
||||
// that value is propagated everywhere. For now, we duplicate the value here.
|
||||
sourceIndices: schema.object(
|
||||
{
|
||||
logs: schema.string({ defaultValue: INDEX_DEFAULTS.logs }),
|
||||
},
|
||||
{ defaultValue: INDEX_DEFAULTS }
|
||||
),
|
||||
});
|
||||
|
||||
export type AssetManagerConfig = TypeOf<typeof configSchema>;
|
||||
|
||||
/**
|
||||
* The following map is passed to the server plugin setup under the
|
||||
* exposeToBrowser: option, and controls which of the above config
|
||||
* keys are allow-listed to be available in the browser config.
|
||||
*
|
||||
* NOTE: anything exposed here will be visible in the UI dev tools,
|
||||
* and therefore MUST NOT be anything that is sensitive information!
|
||||
*/
|
||||
export const exposeToBrowserConfig = {
|
||||
alphaEnabled: true,
|
||||
} as const;
|
||||
|
||||
type ValidKeys = keyof {
|
||||
[K in keyof typeof exposeToBrowserConfig as typeof exposeToBrowserConfig[K] extends true
|
||||
? K
|
||||
: never]: true;
|
||||
};
|
||||
|
||||
export type AssetManagerPublicConfig = Pick<AssetManagerConfig, ValidKeys>;
|
|
@ -1,21 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const ASSET_MANAGER_API_BASE = '/api/asset-manager';
|
||||
|
||||
function base(path: string) {
|
||||
return `${ASSET_MANAGER_API_BASE}${path}`;
|
||||
}
|
||||
|
||||
export const GET_ASSETS = base('/assets');
|
||||
export const GET_RELATED_ASSETS = base('/assets/related');
|
||||
export const GET_ASSETS_DIFF = base('/assets/diff');
|
||||
|
||||
export const GET_HOSTS = base('/assets/hosts');
|
||||
export const GET_SERVICES = base('/assets/services');
|
||||
export const GET_CONTAINERS = base('/assets/containers');
|
||||
export const GET_PODS = base('/assets/pods');
|
|
@ -1,315 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import {
|
||||
dateRt,
|
||||
inRangeFromStringRt,
|
||||
datemathStringRt,
|
||||
createLiteralValueFromUndefinedRT,
|
||||
} from '@kbn/io-ts-utils';
|
||||
|
||||
export const assetTypeRT = rt.keyof({
|
||||
'k8s.pod': null,
|
||||
'k8s.cluster': null,
|
||||
'k8s.node': null,
|
||||
});
|
||||
|
||||
export type AssetType = rt.TypeOf<typeof assetTypeRT>;
|
||||
|
||||
export const assetKindRT = rt.keyof({
|
||||
cluster: null,
|
||||
host: null,
|
||||
pod: null,
|
||||
container: null,
|
||||
service: null,
|
||||
});
|
||||
|
||||
export type AssetKind = rt.TypeOf<typeof assetKindRT>;
|
||||
|
||||
export const assetStatusRT = rt.keyof({
|
||||
CREATING: null,
|
||||
ACTIVE: null,
|
||||
DELETING: null,
|
||||
FAILED: null,
|
||||
UPDATING: null,
|
||||
PENDING: null,
|
||||
UNKNOWN: null,
|
||||
});
|
||||
|
||||
export type AssetStatus = rt.TypeOf<typeof assetStatusRT>;
|
||||
|
||||
// https://github.com/gcanti/io-ts/blob/master/index.md#union-of-string-literals
|
||||
export const cloudProviderNameRT = rt.keyof({
|
||||
aws: null,
|
||||
gcp: null,
|
||||
azure: null,
|
||||
other: null,
|
||||
unknown: null,
|
||||
none: null,
|
||||
});
|
||||
|
||||
export type CloudProviderName = rt.TypeOf<typeof cloudProviderNameRT>;
|
||||
|
||||
const withTimestampRT = rt.type({
|
||||
'@timestamp': rt.string,
|
||||
});
|
||||
|
||||
export type WithTimestamp = rt.TypeOf<typeof withTimestampRT>;
|
||||
|
||||
export const ECSDocumentRT = rt.intersection([
|
||||
withTimestampRT,
|
||||
rt.partial({
|
||||
'kubernetes.namespace': rt.string,
|
||||
'kubernetes.pod.name': rt.string,
|
||||
'kubernetes.pod.uid': rt.string,
|
||||
'kubernetes.pod.start_time': rt.string,
|
||||
'kubernetes.node.name': rt.string,
|
||||
'kubernetes.node.start_time': rt.string,
|
||||
'orchestrator.api_version': rt.string,
|
||||
'orchestrator.namespace': rt.string,
|
||||
'orchestrator.organization': rt.string,
|
||||
'orchestrator.type': rt.string,
|
||||
'orchestrator.cluster.id': rt.string,
|
||||
'orchestrator.cluster.name': rt.string,
|
||||
'orchestrator.cluster.url': rt.string,
|
||||
'orchestrator.cluster.version': rt.string,
|
||||
'cloud.provider': cloudProviderNameRT,
|
||||
'cloud.instance.id': rt.string,
|
||||
'cloud.region': rt.string,
|
||||
'cloud.service.name': rt.string,
|
||||
'service.environment': rt.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
export type ECSDocument = rt.TypeOf<typeof ECSDocumentRT>;
|
||||
|
||||
export const assetRT = rt.intersection([
|
||||
ECSDocumentRT,
|
||||
rt.type({
|
||||
'asset.ean': rt.string,
|
||||
'asset.id': rt.string,
|
||||
'asset.kind': assetKindRT,
|
||||
}),
|
||||
// mixed required and optional require separate hashes combined via intersection
|
||||
// https://github.com/gcanti/io-ts/blob/master/index.md#mixing-required-and-optional-props
|
||||
rt.partial({
|
||||
'asset.collection_version': rt.string,
|
||||
'asset.name': rt.string,
|
||||
'asset.type': assetTypeRT,
|
||||
'asset.status': assetStatusRT,
|
||||
'asset.parents': rt.union([rt.string, rt.array(rt.string)]),
|
||||
'asset.children': rt.union([rt.string, rt.array(rt.string)]),
|
||||
'asset.references': rt.union([rt.string, rt.array(rt.string)]),
|
||||
'asset.namespace': rt.string,
|
||||
}),
|
||||
]);
|
||||
|
||||
export type Asset = rt.TypeOf<typeof assetRT>;
|
||||
|
||||
export type AssetWithoutTimestamp = Omit<Asset, '@timestamp'>;
|
||||
|
||||
export interface K8sPod extends WithTimestamp {
|
||||
id: string;
|
||||
name?: string;
|
||||
ean: string;
|
||||
node?: string;
|
||||
cloud?: {
|
||||
provider?: CloudProviderName;
|
||||
region?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface K8sNodeMetricBucket {
|
||||
timestamp: number;
|
||||
date?: string;
|
||||
averageMemoryAvailable: number | null;
|
||||
averageMemoryUsage: number | null;
|
||||
maxMemoryUsage: number | null;
|
||||
averageCpuCoreNs: number | null;
|
||||
maxCpuCoreNs: number | null;
|
||||
}
|
||||
|
||||
export interface K8sNodeLog {
|
||||
timestamp: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface K8sNode extends WithTimestamp {
|
||||
id: string;
|
||||
name?: string;
|
||||
ean: string;
|
||||
pods?: K8sPod[];
|
||||
cluster?: K8sCluster;
|
||||
cloud?: {
|
||||
provider?: CloudProviderName;
|
||||
region?: string;
|
||||
};
|
||||
metrics?: K8sNodeMetricBucket[];
|
||||
logs?: K8sNodeLog[];
|
||||
}
|
||||
|
||||
export interface K8sCluster extends WithTimestamp {
|
||||
name?: string;
|
||||
nodes?: K8sNode[];
|
||||
ean: string;
|
||||
status?: AssetStatus;
|
||||
version?: string;
|
||||
cloud?: {
|
||||
provider?: CloudProviderName;
|
||||
region?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const assetFiltersSingleKindRT = rt.exact(
|
||||
rt.partial({
|
||||
type: rt.union([assetTypeRT, rt.array(assetTypeRT)]),
|
||||
ean: rt.union([rt.string, rt.array(rt.string)]),
|
||||
id: rt.string,
|
||||
parentEan: rt.string,
|
||||
['cloud.provider']: rt.string,
|
||||
['cloud.region']: rt.string,
|
||||
['orchestrator.cluster.name']: rt.string,
|
||||
})
|
||||
);
|
||||
|
||||
export type SingleKindAssetFilters = rt.TypeOf<typeof assetFiltersSingleKindRT>;
|
||||
|
||||
const supportedKindRT = rt.union([rt.literal('host'), rt.literal('service')]);
|
||||
export const assetFiltersRT = rt.intersection([
|
||||
assetFiltersSingleKindRT,
|
||||
rt.partial({ kind: rt.union([supportedKindRT, rt.array(supportedKindRT)]) }),
|
||||
]);
|
||||
|
||||
export type AssetFilters = rt.TypeOf<typeof assetFiltersRT>;
|
||||
|
||||
export const relationRT = rt.union([
|
||||
rt.literal('ancestors'),
|
||||
rt.literal('descendants'),
|
||||
rt.literal('references'),
|
||||
]);
|
||||
|
||||
export type Relation = rt.TypeOf<typeof relationRT>;
|
||||
export type RelationField = keyof Pick<
|
||||
Asset,
|
||||
'asset.children' | 'asset.parents' | 'asset.references'
|
||||
>;
|
||||
|
||||
export const sizeRT = rt.union([
|
||||
inRangeFromStringRt(1, 100),
|
||||
createLiteralValueFromUndefinedRT(10),
|
||||
]);
|
||||
export const assetDateRT = rt.union([dateRt, datemathStringRt]);
|
||||
|
||||
/**
|
||||
* Hosts
|
||||
*/
|
||||
export const getHostAssetsQueryOptionsRT = rt.intersection([
|
||||
rt.strict({ from: assetDateRT }),
|
||||
rt.partial({
|
||||
to: assetDateRT,
|
||||
size: sizeRT,
|
||||
stringFilters: rt.string,
|
||||
filters: assetFiltersSingleKindRT,
|
||||
}),
|
||||
]);
|
||||
export type GetHostAssetsQueryOptions = rt.TypeOf<typeof getHostAssetsQueryOptionsRT>;
|
||||
export const getHostAssetsResponseRT = rt.type({
|
||||
hosts: rt.array(assetRT),
|
||||
});
|
||||
export type GetHostAssetsResponse = rt.TypeOf<typeof getHostAssetsResponseRT>;
|
||||
|
||||
/**
|
||||
* Containers
|
||||
*/
|
||||
export const getContainerAssetsQueryOptionsRT = rt.intersection([
|
||||
rt.strict({ from: assetDateRT }),
|
||||
rt.partial({
|
||||
to: assetDateRT,
|
||||
size: sizeRT,
|
||||
stringFilters: rt.string,
|
||||
filters: assetFiltersSingleKindRT,
|
||||
}),
|
||||
]);
|
||||
export type GetContainerAssetsQueryOptions = rt.TypeOf<typeof getContainerAssetsQueryOptionsRT>;
|
||||
export const getContainerAssetsResponseRT = rt.type({
|
||||
containers: rt.array(assetRT),
|
||||
});
|
||||
export type GetContainerAssetsResponse = rt.TypeOf<typeof getContainerAssetsResponseRT>;
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
export const getServiceAssetsQueryOptionsRT = rt.intersection([
|
||||
rt.strict({ from: assetDateRT }),
|
||||
rt.partial({
|
||||
from: assetDateRT,
|
||||
to: assetDateRT,
|
||||
size: sizeRT,
|
||||
stringFilters: rt.string,
|
||||
filters: assetFiltersSingleKindRT,
|
||||
}),
|
||||
]);
|
||||
|
||||
export type GetServiceAssetsQueryOptions = rt.TypeOf<typeof getServiceAssetsQueryOptionsRT>;
|
||||
export const getServiceAssetsResponseRT = rt.type({
|
||||
services: rt.array(assetRT),
|
||||
});
|
||||
export type GetServiceAssetsResponse = rt.TypeOf<typeof getServiceAssetsResponseRT>;
|
||||
|
||||
/**
|
||||
* Pods
|
||||
*/
|
||||
export const getPodAssetsQueryOptionsRT = rt.intersection([
|
||||
rt.strict({ from: assetDateRT }),
|
||||
rt.partial({
|
||||
to: assetDateRT,
|
||||
size: sizeRT,
|
||||
stringFilters: rt.string,
|
||||
filters: assetFiltersSingleKindRT,
|
||||
}),
|
||||
]);
|
||||
export type GetPodAssetsQueryOptions = rt.TypeOf<typeof getPodAssetsQueryOptionsRT>;
|
||||
export const getPodAssetsResponseRT = rt.type({
|
||||
pods: rt.array(assetRT),
|
||||
});
|
||||
export type GetPodAssetsResponse = rt.TypeOf<typeof getPodAssetsResponseRT>;
|
||||
|
||||
/**
|
||||
* Assets
|
||||
*/
|
||||
export const getAssetsQueryOptionsRT = rt.intersection([
|
||||
rt.strict({ from: assetDateRT }),
|
||||
rt.partial({
|
||||
to: assetDateRT,
|
||||
size: sizeRT,
|
||||
stringFilters: rt.string,
|
||||
filters: assetFiltersRT,
|
||||
}),
|
||||
]);
|
||||
export type GetAssetsQueryOptions = rt.TypeOf<typeof getAssetsQueryOptionsRT>;
|
||||
export const getAssetsResponseRT = rt.type({
|
||||
assets: rt.array(assetRT),
|
||||
});
|
||||
export type GetAssetsResponse = rt.TypeOf<typeof getAssetsResponseRT>;
|
||||
|
||||
/**
|
||||
* Managed entities enablement
|
||||
*/
|
||||
export const managedEntityEnabledResponseRT = rt.type({
|
||||
enabled: rt.boolean,
|
||||
reason: rt.string,
|
||||
});
|
||||
export type ManagedEntityEnabledResponse = rt.TypeOf<typeof managedEntityEnabledResponseRT>;
|
||||
|
||||
export const managedEntityResponseBase = rt.type({
|
||||
success: rt.boolean,
|
||||
reason: rt.string,
|
||||
message: rt.string,
|
||||
});
|
||||
export type EnableManagedEntityResponse = rt.TypeOf<typeof managedEntityResponseBase>;
|
||||
export type DisableManagedEntityResponse = rt.TypeOf<typeof managedEntityResponseBase>;
|
|
@ -1,24 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AssetFilters, SingleKindAssetFilters } from './types_api';
|
||||
|
||||
export interface SharedAssetsOptionsPublic<F = AssetFilters> {
|
||||
from: string;
|
||||
to?: string;
|
||||
filters?: F;
|
||||
stringFilters?: string;
|
||||
}
|
||||
|
||||
// Methods that return only a single "kind" of asset should not accept
|
||||
// a filter of "kind" to filter by asset kinds
|
||||
|
||||
export type GetHostsOptionsPublic = SharedAssetsOptionsPublic<SingleKindAssetFilters>;
|
||||
export type GetContainersOptionsPublic = SharedAssetsOptionsPublic<SingleKindAssetFilters>;
|
||||
export type GetPodsOptionsPublic = SharedAssetsOptionsPublic<SingleKindAssetFilters>;
|
||||
export type GetServicesOptionsPublic = SharedAssetsOptionsPublic<SingleKindAssetFilters>;
|
||||
export type GetAssetsOptionsPublic = SharedAssetsOptionsPublic<AssetFilters>;
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { HttpSetupMock } from '@kbn/core-http-browser-mocks';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { PublicAssetsClient } from './public_assets_client';
|
||||
import * as routePaths from '../../common/constants_routes';
|
||||
|
||||
describe('Public assets client', () => {
|
||||
let http: HttpSetupMock = coreMock.createSetup().http;
|
||||
|
||||
beforeEach(() => {
|
||||
http = coreMock.createSetup().http;
|
||||
});
|
||||
|
||||
describe('class instantiation', () => {
|
||||
it('should successfully instantiate', () => {
|
||||
new PublicAssetsClient(http);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getHosts', () => {
|
||||
it('should call the REST API', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
await client.getHosts({ from: 'x', to: 'y' });
|
||||
expect(http.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should include specified "from" and "to" parameters in http.get query', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
await client.getHosts({ from: 'x', to: 'y' });
|
||||
expect(http.get).toBeCalledWith(routePaths.GET_HOSTS, {
|
||||
query: { from: 'x', to: 'y' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should include provided filters, but in string form', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
const filters = { id: '*id-1*' };
|
||||
await client.getHosts({ from: 'x', filters });
|
||||
expect(http.get).toBeCalledWith(routePaths.GET_HOSTS, {
|
||||
query: {
|
||||
from: 'x',
|
||||
stringFilters: JSON.stringify(filters),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the direct results of http.get', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
http.get.mockResolvedValueOnce('my hosts');
|
||||
const result = await client.getHosts({ from: 'x', to: 'y' });
|
||||
expect(result).toBe('my hosts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getContainers', () => {
|
||||
it('should call the REST API', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
await client.getContainers({ from: 'x', to: 'y' });
|
||||
expect(http.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should include specified "from" and "to" parameters in http.get query', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
await client.getContainers({ from: 'x', to: 'y' });
|
||||
expect(http.get).toBeCalledWith(routePaths.GET_CONTAINERS, {
|
||||
query: { from: 'x', to: 'y' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should include provided filters, but in string form', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
const filters = { id: '*id-1*' };
|
||||
await client.getContainers({ from: 'x', filters });
|
||||
expect(http.get).toBeCalledWith(routePaths.GET_CONTAINERS, {
|
||||
query: {
|
||||
from: 'x',
|
||||
stringFilters: JSON.stringify(filters),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the direct results of http.get', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
http.get.mockResolvedValueOnce('my hosts');
|
||||
const result = await client.getContainers({ from: 'x', to: 'y' });
|
||||
expect(result).toBe('my hosts');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getServices', () => {
|
||||
it('should call the REST API', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
await client.getServices({ from: 'x', to: 'y' });
|
||||
expect(http.get).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should include specified "from" and "to" parameters in http.get query', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
await client.getServices({ from: 'x', to: 'y' });
|
||||
expect(http.get).toBeCalledWith(routePaths.GET_SERVICES, {
|
||||
query: { from: 'x', to: 'y' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should include provided filters, but in string form', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
const filters = { id: '*id-1*', parentEan: 'container:123' };
|
||||
await client.getServices({ from: 'x', filters });
|
||||
expect(http.get).toBeCalledWith(routePaths.GET_SERVICES, {
|
||||
query: {
|
||||
from: 'x',
|
||||
stringFilters: JSON.stringify(filters),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the direct results of http.get', async () => {
|
||||
const client = new PublicAssetsClient(http);
|
||||
http.get.mockResolvedValueOnce('my services');
|
||||
const result = await client.getServices({ from: 'x', to: 'y' });
|
||||
expect(result).toBe('my services');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,94 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { HttpStart } from '@kbn/core/public';
|
||||
import {
|
||||
GetContainersOptionsPublic,
|
||||
GetHostsOptionsPublic,
|
||||
GetServicesOptionsPublic,
|
||||
GetPodsOptionsPublic,
|
||||
GetAssetsOptionsPublic,
|
||||
} from '../../common/types_client';
|
||||
import {
|
||||
GetContainerAssetsResponse,
|
||||
GetHostAssetsResponse,
|
||||
GetServiceAssetsResponse,
|
||||
GetPodAssetsResponse,
|
||||
GetAssetsResponse,
|
||||
} from '../../common/types_api';
|
||||
import {
|
||||
GET_CONTAINERS,
|
||||
GET_HOSTS,
|
||||
GET_SERVICES,
|
||||
GET_PODS,
|
||||
GET_ASSETS,
|
||||
} from '../../common/constants_routes';
|
||||
import { IPublicAssetsClient } from '../types';
|
||||
|
||||
export class PublicAssetsClient implements IPublicAssetsClient {
|
||||
constructor(private readonly http: HttpStart) {}
|
||||
|
||||
async getHosts(options: GetHostsOptionsPublic) {
|
||||
const { filters, ...otherOptions } = options;
|
||||
const results = await this.http.get<GetHostAssetsResponse>(GET_HOSTS, {
|
||||
query: {
|
||||
stringFilters: JSON.stringify(filters),
|
||||
...otherOptions,
|
||||
},
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getContainers(options: GetContainersOptionsPublic) {
|
||||
const { filters, ...otherOptions } = options;
|
||||
const results = await this.http.get<GetContainerAssetsResponse>(GET_CONTAINERS, {
|
||||
query: {
|
||||
stringFilters: JSON.stringify(filters),
|
||||
...otherOptions,
|
||||
},
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getServices(options: GetServicesOptionsPublic) {
|
||||
const { filters, ...otherOptions } = options;
|
||||
const results = await this.http.get<GetServiceAssetsResponse>(GET_SERVICES, {
|
||||
query: {
|
||||
stringFilters: JSON.stringify(filters),
|
||||
...otherOptions,
|
||||
},
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getPods(options: GetPodsOptionsPublic) {
|
||||
const { filters, ...otherOptions } = options;
|
||||
const results = await this.http.get<GetPodAssetsResponse>(GET_PODS, {
|
||||
query: {
|
||||
stringFilters: JSON.stringify(filters),
|
||||
...otherOptions,
|
||||
},
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async getAssets(options: GetAssetsOptionsPublic) {
|
||||
const { filters, ...otherOptions } = options;
|
||||
const results = await this.http.get<GetAssetsResponse>(GET_ASSETS, {
|
||||
query: {
|
||||
stringFilters: JSON.stringify(filters),
|
||||
...otherOptions,
|
||||
},
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
|
@ -1,8 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const ASSETS_INDEX_PREFIX = 'assets';
|
|
@ -1,24 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from '@kbn/core-plugins-server';
|
||||
import { AssetManagerConfig } from '../common/config';
|
||||
import { AssetManagerServerPluginSetup, AssetManagerServerPluginStart, config } from './plugin';
|
||||
import type { WriteSamplesPostBody } from './routes/sample_assets';
|
||||
|
||||
export type {
|
||||
AssetManagerConfig,
|
||||
WriteSamplesPostBody,
|
||||
AssetManagerServerPluginSetup,
|
||||
AssetManagerServerPluginStart,
|
||||
};
|
||||
export { config };
|
||||
|
||||
export const plugin = async (context: PluginInitializerContext<AssetManagerConfig>) => {
|
||||
const { AssetManagerServerPlugin } = await import('./plugin');
|
||||
return new AssetManagerServerPlugin(context);
|
||||
};
|
|
@ -1,357 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
|
||||
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
import { GetApmIndicesMethod } from '../../asset_client_types';
|
||||
import { getContainers } from './get_containers';
|
||||
import {
|
||||
createGetApmIndicesMock,
|
||||
expectToThrowValidationErrorWithStatusCode,
|
||||
} from '../../../test_utils';
|
||||
import { MetricsDataClient, MetricsDataClientMock } from '@kbn/metrics-data-access-plugin/server';
|
||||
import { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
function createBaseOptions({
|
||||
getApmIndicesMock,
|
||||
metricsDataClientMock,
|
||||
}: {
|
||||
getApmIndicesMock: GetApmIndicesMethod;
|
||||
metricsDataClientMock: MetricsDataClient;
|
||||
}) {
|
||||
return {
|
||||
sourceIndices: {
|
||||
logs: 'my-logs*',
|
||||
},
|
||||
getApmIndices: getApmIndicesMock,
|
||||
metricsClient: metricsDataClientMock,
|
||||
};
|
||||
}
|
||||
|
||||
describe('getContainers', () => {
|
||||
let getApmIndicesMock = createGetApmIndicesMock();
|
||||
let metricsDataClientMock = MetricsDataClientMock.create();
|
||||
let baseOptions = createBaseOptions({ getApmIndicesMock, metricsDataClientMock });
|
||||
let esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
let soClientMock = savedObjectsClientMock.create();
|
||||
|
||||
function resetMocks() {
|
||||
getApmIndicesMock = createGetApmIndicesMock();
|
||||
metricsDataClientMock = MetricsDataClientMock.create();
|
||||
baseOptions = createBaseOptions({ getApmIndicesMock, metricsDataClientMock });
|
||||
esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
soClientMock = savedObjectsClientMock.create();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetMocks();
|
||||
|
||||
// ES returns no results, just enough structure to not blow up
|
||||
esClientMock.search.mockResolvedValueOnce({
|
||||
took: 1,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
failed: 0,
|
||||
successful: 1,
|
||||
total: 1,
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should query Elasticsearch correctly', async () => {
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-5d',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
expect(metricsDataClientMock.getMetricIndices).toHaveBeenCalledTimes(1);
|
||||
expect(metricsDataClientMock.getMetricIndices).toHaveBeenCalledWith({
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.filter).toEqual([
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5d',
|
||||
lte: 'now-3d',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.must).toEqual([
|
||||
{
|
||||
exists: {
|
||||
field: 'container.id',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.should).toEqual([
|
||||
{ exists: { field: 'kubernetes.container.id' } },
|
||||
{ exists: { field: 'kubernetes.pod.uid' } },
|
||||
{ exists: { field: 'kubernetes.node.name' } },
|
||||
{ exists: { field: 'host.hostname' } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly include an EAN filter as a container ID term query', async () => {
|
||||
const mockContainerId = '123abc';
|
||||
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `container:${mockContainerId}`,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'container.id',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'container.id': mockContainerId,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not query ES and return empty if filtering on non-container EAN', async () => {
|
||||
const mockId = 'some-id-123';
|
||||
|
||||
const result = await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `pod:${mockId}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(esClientMock.search).toHaveBeenCalledTimes(0);
|
||||
expect(result).toEqual({ containers: [] });
|
||||
});
|
||||
|
||||
it('should throw an error when an invalid EAN is provided', async () => {
|
||||
try {
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `invalid`,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const hasMessage = 'message' in error;
|
||||
expect(hasMessage).toEqual(true);
|
||||
expect(error.message).toEqual('invalid is not a valid EAN');
|
||||
}
|
||||
|
||||
try {
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `invalid:toomany:colons`,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const hasMessage = 'message' in error;
|
||||
expect(hasMessage).toEqual(true);
|
||||
expect(error.message).toEqual('invalid:toomany:colons is not a valid EAN');
|
||||
}
|
||||
});
|
||||
|
||||
it('should include a wildcard ID filter when an ID filter is provided with asterisks included', async () => {
|
||||
const mockIdPattern = '*partial-id*';
|
||||
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
id: mockIdPattern,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'container.id',
|
||||
},
|
||||
},
|
||||
{
|
||||
wildcard: {
|
||||
'container.id': mockIdPattern,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should include a term ID filter when an ID filter is provided without asterisks included', async () => {
|
||||
const mockId = 'full-id';
|
||||
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
id: mockId,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'container.id',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'container.id': mockId,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should include a term filter for cloud filters', async () => {
|
||||
const mockCloudProvider = 'gcp';
|
||||
const mockCloudRegion = 'us-central-1';
|
||||
|
||||
await getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
'cloud.provider': mockCloudProvider,
|
||||
'cloud.region': mockCloudRegion,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'container.id',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'cloud.provider': mockCloudProvider,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'cloud.region': mockCloudRegion,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "from" date', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-1zz',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "to" date', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getContainers({
|
||||
...baseOptions,
|
||||
from: 'now-5d',
|
||||
to: 'now-3fe',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is a date that is after "to"', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getContainers({
|
||||
...baseOptions,
|
||||
from: 'now',
|
||||
to: 'now-5d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is in the future', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getContainers({
|
||||
...baseOptions,
|
||||
from: 'now+1d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,91 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { Asset } from '../../../../common/types_api';
|
||||
import { GetContainersOptionsPublic } from '../../../../common/types_client';
|
||||
import {
|
||||
AssetClientDependencies,
|
||||
AssetClientOptionsWithInjectedValues,
|
||||
} from '../../asset_client_types';
|
||||
import { parseEan } from '../../parse_ean';
|
||||
import { collectContainers } from '../../collectors';
|
||||
import { validateStringDateRange } from '../../validators/validate_date_range';
|
||||
|
||||
export type GetContainersOptions = GetContainersOptionsPublic & AssetClientDependencies;
|
||||
export type GetContainersOptionsInjected =
|
||||
AssetClientOptionsWithInjectedValues<GetContainersOptions>;
|
||||
|
||||
export async function getContainers(
|
||||
options: GetContainersOptionsInjected
|
||||
): Promise<{ containers: Asset[] }> {
|
||||
validateStringDateRange(options.from, options.to);
|
||||
|
||||
const metricsIndices = await options.metricsClient.getMetricIndices({
|
||||
savedObjectsClient: options.savedObjectsClient,
|
||||
});
|
||||
|
||||
const filters: QueryDslQueryContainer[] = [];
|
||||
|
||||
if (options.filters?.ean) {
|
||||
const ean = Array.isArray(options.filters.ean) ? options.filters.ean[0] : options.filters.ean;
|
||||
const { kind, id } = parseEan(ean);
|
||||
|
||||
// if EAN filter isn't targeting a container asset, we don't need to do this query
|
||||
if (kind !== 'container') {
|
||||
return {
|
||||
containers: [],
|
||||
};
|
||||
}
|
||||
|
||||
filters.push({
|
||||
term: {
|
||||
'container.id': id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.id) {
|
||||
const fn = options.filters.id.includes('*') ? 'wildcard' : 'term';
|
||||
filters.push({
|
||||
[fn]: {
|
||||
'container.id': options.filters.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.provider']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.provider': options.filters['cloud.provider'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.region']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.region': options.filters['cloud.region'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { assets } = await collectContainers({
|
||||
client: options.elasticsearchClient,
|
||||
from: options.from,
|
||||
to: options.to || 'now',
|
||||
filters,
|
||||
sourceIndices: {
|
||||
metrics: metricsIndices,
|
||||
logs: options.sourceIndices.logs,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
containers: assets,
|
||||
};
|
||||
}
|
|
@ -1,357 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
|
||||
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
import { GetApmIndicesMethod } from '../../asset_client_types';
|
||||
import { getHosts } from './get_hosts';
|
||||
import {
|
||||
createGetApmIndicesMock,
|
||||
expectToThrowValidationErrorWithStatusCode,
|
||||
} from '../../../test_utils';
|
||||
import { MetricsDataClient, MetricsDataClientMock } from '@kbn/metrics-data-access-plugin/server';
|
||||
import { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
function createBaseOptions({
|
||||
getApmIndicesMock,
|
||||
metricsDataClientMock,
|
||||
}: {
|
||||
getApmIndicesMock: GetApmIndicesMethod;
|
||||
metricsDataClientMock: MetricsDataClient;
|
||||
}) {
|
||||
return {
|
||||
sourceIndices: {
|
||||
logs: 'my-logs*',
|
||||
},
|
||||
getApmIndices: getApmIndicesMock,
|
||||
metricsClient: metricsDataClientMock,
|
||||
};
|
||||
}
|
||||
|
||||
describe('getHosts', () => {
|
||||
let getApmIndicesMock = createGetApmIndicesMock();
|
||||
let metricsDataClientMock = MetricsDataClientMock.create();
|
||||
let baseOptions = createBaseOptions({ getApmIndicesMock, metricsDataClientMock });
|
||||
let esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
let soClientMock = savedObjectsClientMock.create();
|
||||
|
||||
function resetMocks() {
|
||||
getApmIndicesMock = createGetApmIndicesMock();
|
||||
metricsDataClientMock = MetricsDataClientMock.create();
|
||||
baseOptions = createBaseOptions({ getApmIndicesMock, metricsDataClientMock });
|
||||
esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
soClientMock = savedObjectsClientMock.create();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetMocks();
|
||||
|
||||
// ES returns no results, just enough structure to not blow up
|
||||
esClientMock.search.mockResolvedValueOnce({
|
||||
took: 1,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
failed: 0,
|
||||
successful: 1,
|
||||
total: 1,
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should query Elasticsearch correctly', async () => {
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-5d',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
expect(metricsDataClientMock.getMetricIndices).toHaveBeenCalledTimes(1);
|
||||
expect(metricsDataClientMock.getMetricIndices).toHaveBeenCalledWith({
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.filter).toEqual([
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5d',
|
||||
lte: 'now-3d',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.must).toEqual([
|
||||
{
|
||||
exists: {
|
||||
field: 'host.hostname',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.should).toEqual([
|
||||
{ exists: { field: 'kubernetes.node.name' } },
|
||||
{ exists: { field: 'kubernetes.pod.uid' } },
|
||||
{ exists: { field: 'container.id' } },
|
||||
{ exists: { field: 'cloud.provider' } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly include an EAN filter as a hostname term query', async () => {
|
||||
const mockHostName = 'some-hostname-123';
|
||||
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `host:${mockHostName}`,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'host.hostname',
|
||||
},
|
||||
},
|
||||
{
|
||||
terms: {
|
||||
'host.hostname': [mockHostName],
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not query ES and return empty if filtering on non-host EAN', async () => {
|
||||
const mockId = 'some-id-123';
|
||||
|
||||
const result = await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `container:${mockId}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(esClientMock.search).toHaveBeenCalledTimes(0);
|
||||
expect(result).toEqual({ hosts: [] });
|
||||
});
|
||||
|
||||
it('should throw an error when an invalid EAN is provided', async () => {
|
||||
try {
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `invalid`,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const hasMessage = 'message' in error;
|
||||
expect(hasMessage).toEqual(true);
|
||||
expect(error.message).toEqual('invalid is not a valid EAN');
|
||||
}
|
||||
|
||||
try {
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `invalid:toomany:colons`,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const hasMessage = 'message' in error;
|
||||
expect(hasMessage).toEqual(true);
|
||||
expect(error.message).toEqual('invalid:toomany:colons is not a valid EAN');
|
||||
}
|
||||
});
|
||||
|
||||
it('should include a wildcard ID filter when an ID filter is provided with asterisks included', async () => {
|
||||
const mockIdPattern = '*partial-id*';
|
||||
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
id: mockIdPattern,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'host.hostname',
|
||||
},
|
||||
},
|
||||
{
|
||||
wildcard: {
|
||||
'host.hostname': mockIdPattern,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should include a term ID filter when an ID filter is provided without asterisks included', async () => {
|
||||
const mockId = 'full-id';
|
||||
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
id: mockId,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'host.hostname',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'host.hostname': mockId,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should include a term filter for cloud filters', async () => {
|
||||
const mockCloudProvider = 'gcp';
|
||||
const mockCloudRegion = 'us-central-1';
|
||||
|
||||
await getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
'cloud.provider': mockCloudProvider,
|
||||
'cloud.region': mockCloudRegion,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'host.hostname',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'cloud.provider': mockCloudProvider,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'cloud.region': mockCloudRegion,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "from" date', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-1zz',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "to" date', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getHosts({
|
||||
...baseOptions,
|
||||
from: 'now-5d',
|
||||
to: 'now-3fe',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is a date that is after "to"', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getHosts({
|
||||
...baseOptions,
|
||||
from: 'now',
|
||||
to: 'now-5d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is in the future', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getHosts({
|
||||
...baseOptions,
|
||||
from: 'now+1d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,91 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { Asset } from '../../../../common/types_api';
|
||||
import { collectHosts } from '../../collectors/hosts';
|
||||
import { GetHostsOptionsPublic } from '../../../../common/types_client';
|
||||
import {
|
||||
AssetClientDependencies,
|
||||
AssetClientOptionsWithInjectedValues,
|
||||
} from '../../asset_client_types';
|
||||
import { parseEan } from '../../parse_ean';
|
||||
import { validateStringDateRange } from '../../validators/validate_date_range';
|
||||
|
||||
export type GetHostsOptions = GetHostsOptionsPublic & AssetClientDependencies;
|
||||
export type GetHostsOptionsInjected = AssetClientOptionsWithInjectedValues<GetHostsOptions>;
|
||||
|
||||
export async function getHosts(options: GetHostsOptionsInjected): Promise<{ hosts: Asset[] }> {
|
||||
validateStringDateRange(options.from, options.to);
|
||||
|
||||
const metricsIndices = await options.metricsClient.getMetricIndices({
|
||||
savedObjectsClient: options.savedObjectsClient,
|
||||
});
|
||||
|
||||
const filters: QueryDslQueryContainer[] = [];
|
||||
|
||||
if (options.filters?.ean) {
|
||||
const eans = Array.isArray(options.filters.ean) ? options.filters.ean : [options.filters.ean];
|
||||
const hostnames = eans
|
||||
.map(parseEan)
|
||||
.filter(({ kind }) => kind === 'host')
|
||||
.map(({ id }) => id);
|
||||
|
||||
// if EAN filter isn't targeting a host asset, we don't need to do this query
|
||||
if (hostnames.length === 0) {
|
||||
return {
|
||||
hosts: [],
|
||||
};
|
||||
}
|
||||
|
||||
filters.push({
|
||||
terms: {
|
||||
'host.hostname': hostnames,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.id) {
|
||||
const fn = options.filters.id.includes('*') ? 'wildcard' : 'term';
|
||||
filters.push({
|
||||
[fn]: {
|
||||
'host.hostname': options.filters.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.provider']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.provider': options.filters['cloud.provider'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.region']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.region': options.filters['cloud.region'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { assets } = await collectHosts({
|
||||
client: options.elasticsearchClient,
|
||||
from: options.from,
|
||||
to: options.to || 'now',
|
||||
filters,
|
||||
sourceIndices: {
|
||||
metrics: metricsIndices,
|
||||
logs: options.sourceIndices.logs,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
hosts: assets,
|
||||
};
|
||||
}
|
|
@ -1,341 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
|
||||
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
import { GetApmIndicesMethod } from '../../asset_client_types';
|
||||
import { getPods } from './get_pods';
|
||||
import {
|
||||
createGetApmIndicesMock,
|
||||
expectToThrowValidationErrorWithStatusCode,
|
||||
} from '../../../test_utils';
|
||||
import { MetricsDataClient, MetricsDataClientMock } from '@kbn/metrics-data-access-plugin/server';
|
||||
import { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
function createBaseOptions({
|
||||
getApmIndicesMock,
|
||||
metricsDataClientMock,
|
||||
}: {
|
||||
getApmIndicesMock: GetApmIndicesMethod;
|
||||
metricsDataClientMock: MetricsDataClient;
|
||||
}) {
|
||||
return {
|
||||
sourceIndices: {
|
||||
logs: 'my-logs*',
|
||||
},
|
||||
getApmIndices: getApmIndicesMock,
|
||||
metricsClient: metricsDataClientMock,
|
||||
};
|
||||
}
|
||||
|
||||
describe('getPods', () => {
|
||||
let getApmIndicesMock = createGetApmIndicesMock();
|
||||
let metricsDataClientMock = MetricsDataClientMock.create();
|
||||
let baseOptions = createBaseOptions({ getApmIndicesMock, metricsDataClientMock });
|
||||
let esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
let soClientMock = savedObjectsClientMock.create();
|
||||
|
||||
function resetMocks() {
|
||||
getApmIndicesMock = createGetApmIndicesMock();
|
||||
metricsDataClientMock = MetricsDataClientMock.create();
|
||||
baseOptions = createBaseOptions({ getApmIndicesMock, metricsDataClientMock });
|
||||
esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
soClientMock = savedObjectsClientMock.create();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetMocks();
|
||||
|
||||
// ES returns no results, just enough structure to not blow up
|
||||
esClientMock.search.mockResolvedValueOnce({
|
||||
took: 1,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
failed: 0,
|
||||
successful: 1,
|
||||
total: 1,
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should query Elasticsearch correctly', async () => {
|
||||
await getPods({
|
||||
...baseOptions,
|
||||
from: 'now-5d',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
expect(metricsDataClientMock.getMetricIndices).toHaveBeenCalledTimes(1);
|
||||
expect(metricsDataClientMock.getMetricIndices).toHaveBeenCalledWith({
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.filter).toEqual([
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5d',
|
||||
lte: 'now-3d',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.must).toEqual([
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.pod.uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.node.name',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should correctly include an EAN filter as a pod ID term query', async () => {
|
||||
const mockPodId = '123abc';
|
||||
|
||||
await getPods({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `pod:${mockPodId}`,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.pod.uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.node.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kubernetes.pod.uid': mockPodId,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not query ES and return empty if filtering on non-pod EAN', async () => {
|
||||
const mockId = 'some-id-123';
|
||||
|
||||
const result = await getPods({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
ean: `container:${mockId}`,
|
||||
},
|
||||
});
|
||||
|
||||
expect(esClientMock.search).toHaveBeenCalledTimes(0);
|
||||
expect(result).toEqual({ pods: [] });
|
||||
});
|
||||
|
||||
it('should include a wildcard ID filter when an ID filter is provided with asterisks included', async () => {
|
||||
const mockIdPattern = '*partial-id*';
|
||||
|
||||
await getPods({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
id: mockIdPattern,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.pod.uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.node.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
wildcard: {
|
||||
'kubernetes.pod.uid': mockIdPattern,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should include a term ID filter when an ID filter is provided without asterisks included', async () => {
|
||||
const mockId = 'full-id';
|
||||
|
||||
await getPods({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
id: mockId,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.pod.uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.node.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kubernetes.pod.uid': mockId,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should include a term filter for cloud filters', async () => {
|
||||
const mockCloudProvider = 'gcp';
|
||||
const mockCloudRegion = 'us-central-1';
|
||||
|
||||
await getPods({
|
||||
...baseOptions,
|
||||
from: 'now-1h',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
filters: {
|
||||
'cloud.provider': mockCloudProvider,
|
||||
'cloud.region': mockCloudRegion,
|
||||
},
|
||||
});
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.must).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.pod.uid',
|
||||
},
|
||||
},
|
||||
{
|
||||
exists: {
|
||||
field: 'kubernetes.node.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'cloud.provider': mockCloudProvider,
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'cloud.region': mockCloudRegion,
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "from" date', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getPods({
|
||||
...baseOptions,
|
||||
from: 'now-1zz',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "to" date', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getPods({
|
||||
...baseOptions,
|
||||
from: 'now-5d',
|
||||
to: 'now-3fe',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is a date that is after "to"', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getPods({
|
||||
...baseOptions,
|
||||
from: 'now',
|
||||
to: 'now-5d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is in the future', () => {
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
getPods({
|
||||
...baseOptions,
|
||||
from: 'now+1d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,96 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { Asset } from '../../../../common/types_api';
|
||||
import { GetPodsOptionsPublic } from '../../../../common/types_client';
|
||||
import {
|
||||
AssetClientDependencies,
|
||||
AssetClientOptionsWithInjectedValues,
|
||||
} from '../../asset_client_types';
|
||||
import { parseEan } from '../../parse_ean';
|
||||
import { collectPods } from '../../collectors/pods';
|
||||
import { validateStringDateRange } from '../../validators/validate_date_range';
|
||||
|
||||
export type GetPodsOptions = GetPodsOptionsPublic & AssetClientDependencies;
|
||||
export type GetPodsOptionsInjected = AssetClientOptionsWithInjectedValues<GetPodsOptions>;
|
||||
|
||||
export async function getPods(options: GetPodsOptionsInjected): Promise<{ pods: Asset[] }> {
|
||||
validateStringDateRange(options.from, options.to);
|
||||
|
||||
const metricsIndices = await options.metricsClient.getMetricIndices({
|
||||
savedObjectsClient: options.savedObjectsClient,
|
||||
});
|
||||
|
||||
const filters: QueryDslQueryContainer[] = [];
|
||||
|
||||
if (options.filters?.ean) {
|
||||
const ean = Array.isArray(options.filters.ean) ? options.filters.ean[0] : options.filters.ean;
|
||||
const { kind, id } = parseEan(ean);
|
||||
|
||||
// if EAN filter isn't targeting a pod asset, we don't need to do this query
|
||||
if (kind !== 'pod') {
|
||||
return {
|
||||
pods: [],
|
||||
};
|
||||
}
|
||||
|
||||
filters.push({
|
||||
term: {
|
||||
'kubernetes.pod.uid': id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.id) {
|
||||
const fn = options.filters.id.includes('*') ? 'wildcard' : 'term';
|
||||
filters.push({
|
||||
[fn]: {
|
||||
'kubernetes.pod.uid': options.filters.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['orchestrator.cluster.name']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'orchestrator.cluster.name': options.filters['orchestrator.cluster.name'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.provider']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.provider': options.filters['cloud.provider'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.region']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.region': options.filters['cloud.region'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { assets } = await collectPods({
|
||||
client: options.elasticsearchClient,
|
||||
from: options.from,
|
||||
to: options.to || 'now',
|
||||
filters,
|
||||
sourceIndices: {
|
||||
metrics: metricsIndices,
|
||||
logs: options.sourceIndices.logs,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
pods: assets,
|
||||
};
|
||||
}
|
|
@ -1,108 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { Asset } from '../../../../common/types_api';
|
||||
import { collectServices } from '../../collectors/services';
|
||||
import { parseEan } from '../../parse_ean';
|
||||
import { GetServicesOptionsPublic } from '../../../../common/types_client';
|
||||
import {
|
||||
AssetClientDependencies,
|
||||
AssetClientOptionsWithInjectedValues,
|
||||
} from '../../asset_client_types';
|
||||
import { validateStringDateRange } from '../../validators/validate_date_range';
|
||||
|
||||
export type GetServicesOptions = GetServicesOptionsPublic & AssetClientDependencies;
|
||||
export type GetServicesOptionsInjected = AssetClientOptionsWithInjectedValues<GetServicesOptions>;
|
||||
|
||||
export async function getServices(
|
||||
options: GetServicesOptionsInjected
|
||||
): Promise<{ services: Asset[] }> {
|
||||
validateStringDateRange(options.from, options.to);
|
||||
|
||||
const filters: QueryDslQueryContainer[] = [];
|
||||
|
||||
if (options.filters?.ean) {
|
||||
const eans = Array.isArray(options.filters.ean) ? options.filters.ean : [options.filters.ean];
|
||||
const services = eans
|
||||
.map(parseEan)
|
||||
.filter(({ kind }) => kind === 'service')
|
||||
.map(({ id }) => id);
|
||||
|
||||
if (services.length === 0) {
|
||||
return {
|
||||
services: [],
|
||||
};
|
||||
}
|
||||
|
||||
filters.push({
|
||||
terms: {
|
||||
'service.name': services,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.parentEan) {
|
||||
const { kind, id } = parseEan(options.filters?.parentEan);
|
||||
|
||||
if (kind === 'host') {
|
||||
filters.push({
|
||||
bool: {
|
||||
should: [{ term: { 'host.name': id } }, { term: { 'host.hostname': id } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (kind === 'container') {
|
||||
filters.push({
|
||||
bool: {
|
||||
should: [{ term: { 'container.id': id } }],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (options.filters?.id) {
|
||||
const fn = options.filters.id.includes('*') ? 'wildcard' : 'term';
|
||||
filters.push({
|
||||
[fn]: {
|
||||
'service.name': options.filters.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.provider']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.provider': options.filters['cloud.provider'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (options.filters?.['cloud.region']) {
|
||||
filters.push({
|
||||
term: {
|
||||
'cloud.region': options.filters['cloud.region'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const apmIndices = await options.getApmIndices(options.savedObjectsClient);
|
||||
const { assets } = await collectServices({
|
||||
client: options.elasticsearchClient,
|
||||
from: options.from,
|
||||
to: options.to || 'now',
|
||||
sourceIndices: {
|
||||
apm: apmIndices,
|
||||
},
|
||||
filters,
|
||||
});
|
||||
|
||||
return { services: assets };
|
||||
}
|
|
@ -1,167 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
ElasticsearchClientMock,
|
||||
elasticsearchClientMock,
|
||||
} from '@kbn/core-elasticsearch-client-server-mocks';
|
||||
import { AssetClient } from './asset_client';
|
||||
import { MetricsDataClient, MetricsDataClientMock } from '@kbn/metrics-data-access-plugin/server';
|
||||
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
|
||||
import { SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { AssetsValidationError } from './validators/validation_error';
|
||||
import { GetApmIndicesMethod } from './asset_client_types';
|
||||
import { createGetApmIndicesMock, expectToThrowValidationErrorWithStatusCode } from '../test_utils';
|
||||
|
||||
function createAssetClient(
|
||||
metricsDataClient: MetricsDataClient,
|
||||
getApmIndicesMock: jest.Mocked<GetApmIndicesMethod>
|
||||
) {
|
||||
return new AssetClient({
|
||||
sourceIndices: {
|
||||
logs: 'my-logs*',
|
||||
},
|
||||
getApmIndices: getApmIndicesMock,
|
||||
metricsClient: metricsDataClient,
|
||||
});
|
||||
}
|
||||
|
||||
describe('Server assets client', () => {
|
||||
let metricsDataClientMock: MetricsDataClient = MetricsDataClientMock.create();
|
||||
let getApmIndicesMock: jest.Mocked<GetApmIndicesMethod> = createGetApmIndicesMock();
|
||||
let esClientMock: ElasticsearchClientMock =
|
||||
elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
let soClientMock: jest.Mocked<SavedObjectsClientContract>;
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mocks
|
||||
esClientMock = elasticsearchClientMock.createScopedClusterClient().asCurrentUser;
|
||||
soClientMock = savedObjectsClientMock.create();
|
||||
metricsDataClientMock = MetricsDataClientMock.create();
|
||||
getApmIndicesMock = createGetApmIndicesMock();
|
||||
|
||||
// ES returns no results, just enough structure to not blow up
|
||||
esClientMock.search.mockResolvedValueOnce({
|
||||
took: 1,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
failed: 0,
|
||||
successful: 1,
|
||||
total: 1,
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('class instantiation', () => {
|
||||
it('should successfully instantiate', () => {
|
||||
createAssetClient(metricsDataClientMock, getApmIndicesMock);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Move this block to the get_services accessor folder
|
||||
describe('getServices', () => {
|
||||
it('should query Elasticsearch correctly', async () => {
|
||||
const client = createAssetClient(metricsDataClientMock, getApmIndicesMock);
|
||||
|
||||
await client.getServices({
|
||||
from: 'now-5d',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
});
|
||||
|
||||
expect(getApmIndicesMock).toHaveBeenCalledTimes(1);
|
||||
expect(getApmIndicesMock).toHaveBeenCalledWith(soClientMock);
|
||||
|
||||
const dsl = esClientMock.search.mock.lastCall?.[0] as SearchRequest | undefined;
|
||||
const { bool } = dsl?.query || {};
|
||||
expect(bool).toBeDefined();
|
||||
|
||||
expect(bool?.filter).toEqual([
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5d',
|
||||
lte: 'now-3d',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.must).toEqual([
|
||||
{
|
||||
exists: {
|
||||
field: 'service.name',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(bool?.should).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "from" date', () => {
|
||||
const client = createAssetClient(metricsDataClientMock, getApmIndicesMock);
|
||||
|
||||
return expect(() =>
|
||||
client.getServices({
|
||||
from: 'now-1zz',
|
||||
to: 'now-3d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
})
|
||||
).rejects.toThrow(AssetsValidationError);
|
||||
});
|
||||
|
||||
it('should reject with 400 for invalid "to" date', () => {
|
||||
const client = createAssetClient(metricsDataClientMock, getApmIndicesMock);
|
||||
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
client.getServices({
|
||||
from: 'now-5d',
|
||||
to: 'now-3fe',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is a date that is after "to"', () => {
|
||||
const client = createAssetClient(metricsDataClientMock, getApmIndicesMock);
|
||||
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
client.getServices({
|
||||
from: 'now',
|
||||
to: 'now-5d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject with 400 when "from" is in the future', () => {
|
||||
const client = createAssetClient(metricsDataClientMock, getApmIndicesMock);
|
||||
|
||||
return expectToThrowValidationErrorWithStatusCode(
|
||||
() =>
|
||||
client.getServices({
|
||||
from: 'now+1d',
|
||||
elasticsearchClient: esClientMock,
|
||||
savedObjectsClient: soClientMock,
|
||||
}),
|
||||
{ statusCode: 400 }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,56 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { orderBy } from 'lodash';
|
||||
import { Asset } from '../../common/types_api';
|
||||
import { GetAssetsOptionsPublic } from '../../common/types_client';
|
||||
import { getContainers, GetContainersOptions } from './accessors/containers/get_containers';
|
||||
import { getHosts, GetHostsOptions } from './accessors/hosts/get_hosts';
|
||||
import { getServices, GetServicesOptions } from './accessors/services/get_services';
|
||||
import { getPods, GetPodsOptions } from './accessors/pods/get_pods';
|
||||
import { AssetClientBaseOptions, AssetClientOptionsWithInjectedValues } from './asset_client_types';
|
||||
import { AssetClientDependencies } from './asset_client_types';
|
||||
|
||||
type GetAssetsOptions = GetAssetsOptionsPublic & AssetClientDependencies;
|
||||
|
||||
export class AssetClient {
|
||||
constructor(private baseOptions: AssetClientBaseOptions) {}
|
||||
|
||||
injectOptions<T extends object = {}>(options: T): AssetClientOptionsWithInjectedValues<T> {
|
||||
return {
|
||||
...options,
|
||||
...this.baseOptions,
|
||||
};
|
||||
}
|
||||
|
||||
async getHosts(options: GetHostsOptions): Promise<{ hosts: Asset[] }> {
|
||||
const withInjected = this.injectOptions(options);
|
||||
return await getHosts(withInjected);
|
||||
}
|
||||
|
||||
async getServices(options: GetServicesOptions): Promise<{ services: Asset[] }> {
|
||||
const withInjected = this.injectOptions(options);
|
||||
return await getServices(withInjected);
|
||||
}
|
||||
|
||||
async getContainers(options: GetContainersOptions): Promise<{ containers: Asset[] }> {
|
||||
const withInjected = this.injectOptions(options);
|
||||
return await getContainers(withInjected);
|
||||
}
|
||||
|
||||
async getPods(options: GetPodsOptions): Promise<{ pods: Asset[] }> {
|
||||
const withInjected = this.injectOptions(options);
|
||||
return await getPods(withInjected);
|
||||
}
|
||||
|
||||
async getAssets(options: GetAssetsOptions): Promise<{ assets: Asset[] }> {
|
||||
const withInjected = this.injectOptions(options);
|
||||
const { hosts } = await getHosts(withInjected);
|
||||
const { services } = await getServices(withInjected);
|
||||
return { assets: orderBy(hosts.concat(services), ['@timestamp'], ['desc']) };
|
||||
}
|
||||
}
|
|
@ -1,28 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { APMDataAccessConfig } from '@kbn/apm-data-access-plugin/server';
|
||||
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { MetricsDataClient } from '@kbn/metrics-data-access-plugin/server';
|
||||
import { AssetManagerConfig } from '../../common/config';
|
||||
|
||||
export type GetApmIndicesMethod = (
|
||||
soClient: SavedObjectsClientContract
|
||||
) => Promise<APMDataAccessConfig['indices']>;
|
||||
export interface AssetClientDependencies {
|
||||
elasticsearchClient: ElasticsearchClient;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
export interface AssetClientBaseOptions {
|
||||
sourceIndices: AssetManagerConfig['sourceIndices'];
|
||||
getApmIndices: GetApmIndicesMethod;
|
||||
metricsClient: MetricsDataClient;
|
||||
}
|
||||
|
||||
export type AssetClientOptionsWithInjectedValues<T extends object> = T & AssetClientBaseOptions;
|
|
@ -1,101 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { Asset } from '../../../common/types_api';
|
||||
import { CollectorOptions, QUERY_MAX_SIZE } from '.';
|
||||
import { extractFieldValue } from '../utils';
|
||||
|
||||
export async function collectContainers({
|
||||
client,
|
||||
from,
|
||||
to,
|
||||
sourceIndices,
|
||||
filters = [],
|
||||
afterKey,
|
||||
}: CollectorOptions) {
|
||||
if (!sourceIndices?.metrics || !sourceIndices?.logs) {
|
||||
throw new Error('missing required metrics/logs indices');
|
||||
}
|
||||
|
||||
const musts = [...filters, { exists: { field: 'container.id' } }];
|
||||
|
||||
const { metrics, logs } = sourceIndices;
|
||||
const dsl: estypes.SearchRequest = {
|
||||
index: [metrics, logs],
|
||||
size: QUERY_MAX_SIZE,
|
||||
collapse: {
|
||||
field: 'container.id',
|
||||
},
|
||||
sort: [{ 'container.id': 'asc' }],
|
||||
_source: false,
|
||||
fields: [
|
||||
'@timestamp',
|
||||
'kubernetes.*',
|
||||
'cloud.provider',
|
||||
'orchestrator.cluster.name',
|
||||
'host.name',
|
||||
'host.hostname',
|
||||
],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
must: musts,
|
||||
should: [
|
||||
{ exists: { field: 'kubernetes.container.id' } },
|
||||
{ exists: { field: 'kubernetes.pod.uid' } },
|
||||
{ exists: { field: 'kubernetes.node.name' } },
|
||||
{ exists: { field: 'host.hostname' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (afterKey) {
|
||||
dsl.search_after = afterKey;
|
||||
}
|
||||
|
||||
const esResponse = await client.search(dsl);
|
||||
|
||||
const assets = esResponse.hits.hits.reduce<Asset[]>((acc: Asset[], hit: any) => {
|
||||
const { fields = {} } = hit;
|
||||
const containerId = extractFieldValue(fields['container.id']);
|
||||
const podUid = extractFieldValue(fields['kubernetes.pod.uid']);
|
||||
const nodeName = extractFieldValue(fields['kubernetes.node.name']);
|
||||
|
||||
const parentEan = podUid ? `pod:${podUid}` : `host:${fields['host.hostname']}`;
|
||||
|
||||
const container: Asset = {
|
||||
'@timestamp': extractFieldValue(fields['@timestamp']),
|
||||
'asset.kind': 'container',
|
||||
'asset.id': containerId,
|
||||
'asset.ean': `container:${containerId}`,
|
||||
'asset.parents': [parentEan],
|
||||
};
|
||||
|
||||
if (nodeName) {
|
||||
container['asset.references'] = [`host:${nodeName}`];
|
||||
}
|
||||
|
||||
acc.push(container);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const hitsLen = esResponse.hits.hits.length;
|
||||
const next = hitsLen === QUERY_MAX_SIZE ? esResponse.hits.hits[hitsLen - 1].sort : undefined;
|
||||
return { assets, afterKey: next };
|
||||
}
|
|
@ -1,119 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { Asset } from '../../../common/types_api';
|
||||
import { CollectorOptions, QUERY_MAX_SIZE } from '.';
|
||||
import { extractFieldValue } from '../utils';
|
||||
|
||||
export async function collectHosts({
|
||||
client,
|
||||
from,
|
||||
to,
|
||||
sourceIndices,
|
||||
afterKey,
|
||||
filters = [],
|
||||
}: CollectorOptions) {
|
||||
if (!sourceIndices?.metrics || !sourceIndices?.logs) {
|
||||
throw new Error('missing required metrics/logs indices');
|
||||
}
|
||||
|
||||
const { metrics, logs } = sourceIndices;
|
||||
|
||||
const musts = [...filters, { exists: { field: 'host.hostname' } }];
|
||||
const dsl: estypes.SearchRequest = {
|
||||
index: [metrics, logs],
|
||||
size: QUERY_MAX_SIZE,
|
||||
collapse: { field: 'host.hostname' },
|
||||
sort: [{ 'host.hostname': 'asc' }],
|
||||
_source: false,
|
||||
fields: [
|
||||
'@timestamp',
|
||||
'cloud.*',
|
||||
'container.*',
|
||||
'host.hostname',
|
||||
'kubernetes.*',
|
||||
'orchestrator.cluster.name',
|
||||
],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
must: musts,
|
||||
should: [
|
||||
{ exists: { field: 'kubernetes.node.name' } },
|
||||
{ exists: { field: 'kubernetes.pod.uid' } },
|
||||
{ exists: { field: 'container.id' } },
|
||||
{ exists: { field: 'cloud.provider' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (afterKey) {
|
||||
dsl.search_after = afterKey;
|
||||
}
|
||||
|
||||
const esResponse = await client.search(dsl);
|
||||
|
||||
const assets = esResponse.hits.hits.reduce<Asset[]>((acc: Asset[], hit: any) => {
|
||||
const { fields = {} } = hit;
|
||||
const hostName = extractFieldValue(fields['host.hostname']);
|
||||
const k8sNode = extractFieldValue(fields['kubernetes.node.name']);
|
||||
const k8sPod = extractFieldValue(fields['kubernetes.pod.uid']);
|
||||
|
||||
const hostEan = `host:${k8sNode || hostName}`;
|
||||
|
||||
const host: Asset = {
|
||||
'@timestamp': extractFieldValue(fields['@timestamp']),
|
||||
'asset.kind': 'host',
|
||||
'asset.id': k8sNode || hostName,
|
||||
'asset.name': k8sNode || hostName,
|
||||
'asset.ean': hostEan,
|
||||
};
|
||||
|
||||
if (fields['cloud.provider']) {
|
||||
host['cloud.provider'] = extractFieldValue(fields['cloud.provider']);
|
||||
}
|
||||
|
||||
if (fields['cloud.instance.id']) {
|
||||
host['cloud.instance.id'] = extractFieldValue(fields['cloud.instance.id']);
|
||||
}
|
||||
|
||||
if (fields['cloud.service.name']) {
|
||||
host['cloud.service.name'] = extractFieldValue(fields['cloud.service.name']);
|
||||
}
|
||||
|
||||
if (fields['cloud.region']) {
|
||||
host['cloud.region'] = extractFieldValue(fields['cloud.region']);
|
||||
}
|
||||
|
||||
if (fields['orchestrator.cluster.name']) {
|
||||
host['orchestrator.cluster.name'] = extractFieldValue(fields['orchestrator.cluster.name']);
|
||||
}
|
||||
|
||||
if (k8sPod) {
|
||||
host['asset.children'] = [`pod:${k8sPod}`];
|
||||
}
|
||||
|
||||
acc.push(host);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const hitsLen = esResponse.hits.hits.length;
|
||||
const next = hitsLen === QUERY_MAX_SIZE ? esResponse.hits.hits[hitsLen - 1].sort : undefined;
|
||||
return { assets, afterKey: next };
|
||||
}
|
|
@ -1,38 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import type { APMIndices } from '@kbn/apm-data-access-plugin/server';
|
||||
import { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { Asset } from '../../../common/types_api';
|
||||
|
||||
export const QUERY_MAX_SIZE = 10000;
|
||||
|
||||
export type Collector = (opts: CollectorOptions) => Promise<CollectorResult>;
|
||||
|
||||
export interface CollectorOptions {
|
||||
client: ElasticsearchClient;
|
||||
from: string;
|
||||
to: string;
|
||||
sourceIndices?: {
|
||||
apm?: APMIndices;
|
||||
metrics?: string;
|
||||
logs?: string;
|
||||
};
|
||||
afterKey?: estypes.SortResults;
|
||||
filters?: estypes.QueryDslQueryContainer[];
|
||||
}
|
||||
|
||||
export interface CollectorResult {
|
||||
assets: Asset[];
|
||||
afterKey?: estypes.SortResults;
|
||||
}
|
||||
|
||||
export { collectContainers } from './containers';
|
||||
export { collectHosts } from './hosts';
|
||||
export { collectPods } from './pods';
|
||||
export { collectServices } from './services';
|
|
@ -1,101 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { Asset } from '../../../common/types_api';
|
||||
import { CollectorOptions, QUERY_MAX_SIZE } from '.';
|
||||
import { extractFieldValue } from '../utils';
|
||||
|
||||
export async function collectPods({
|
||||
client,
|
||||
from,
|
||||
to,
|
||||
sourceIndices,
|
||||
filters = [],
|
||||
afterKey,
|
||||
}: CollectorOptions) {
|
||||
if (!sourceIndices?.metrics || !sourceIndices?.logs) {
|
||||
throw new Error('missing required metrics/logs indices');
|
||||
}
|
||||
|
||||
const musts = [
|
||||
...filters,
|
||||
{ exists: { field: 'kubernetes.pod.uid' } },
|
||||
{ exists: { field: 'kubernetes.node.name' } },
|
||||
];
|
||||
|
||||
const { metrics, logs } = sourceIndices;
|
||||
const dsl: estypes.SearchRequest = {
|
||||
index: [metrics, logs],
|
||||
size: QUERY_MAX_SIZE,
|
||||
collapse: {
|
||||
field: 'kubernetes.pod.uid',
|
||||
},
|
||||
sort: [{ 'kubernetes.pod.uid': 'asc' }],
|
||||
_source: false,
|
||||
fields: [
|
||||
'@timestamp',
|
||||
'kubernetes.*',
|
||||
'cloud.provider',
|
||||
'orchestrator.cluster.name',
|
||||
'host.name',
|
||||
'host.hostname',
|
||||
],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
must: musts,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (afterKey) {
|
||||
dsl.search_after = afterKey;
|
||||
}
|
||||
|
||||
const esResponse = await client.search(dsl);
|
||||
|
||||
const assets = esResponse.hits.hits.reduce<Asset[]>((acc: Asset[], hit: any) => {
|
||||
const { fields = {} } = hit;
|
||||
const podUid = extractFieldValue(fields['kubernetes.pod.uid']);
|
||||
const nodeName = extractFieldValue(fields['kubernetes.node.name']);
|
||||
const clusterName = extractFieldValue(fields['orchestrator.cluster.name']);
|
||||
|
||||
const pod: Asset = {
|
||||
'@timestamp': extractFieldValue(fields['@timestamp']),
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': podUid,
|
||||
'asset.ean': `pod:${podUid}`,
|
||||
'asset.parents': [`host:${nodeName}`],
|
||||
};
|
||||
|
||||
if (fields['cloud.provider']) {
|
||||
pod['cloud.provider'] = extractFieldValue(fields['cloud.provider']);
|
||||
}
|
||||
|
||||
if (clusterName) {
|
||||
pod['orchestrator.cluster.name'] = clusterName;
|
||||
}
|
||||
|
||||
acc.push(pod);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const hitsLen = esResponse.hits.hits.length;
|
||||
const next = hitsLen === QUERY_MAX_SIZE ? esResponse.hits.hits[hitsLen - 1].sort : undefined;
|
||||
return { assets, afterKey: next };
|
||||
}
|
|
@ -1,147 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { debug } from '../../../common/debug_log';
|
||||
import { Asset } from '../../../common/types_api';
|
||||
import { CollectorOptions, QUERY_MAX_SIZE } from '.';
|
||||
|
||||
export async function collectServices({
|
||||
client,
|
||||
from,
|
||||
to,
|
||||
sourceIndices,
|
||||
afterKey,
|
||||
filters = [],
|
||||
}: CollectorOptions) {
|
||||
if (!sourceIndices?.apm) {
|
||||
throw new Error('missing required apm indices');
|
||||
}
|
||||
|
||||
const { transaction, error, metric } = sourceIndices.apm;
|
||||
const musts: estypes.QueryDslQueryContainer[] = [
|
||||
...filters,
|
||||
{
|
||||
exists: {
|
||||
field: 'service.name',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const dsl: estypes.SearchRequest = {
|
||||
index: [transaction, error, metric],
|
||||
size: 0,
|
||||
_source: false,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: from,
|
||||
lte: to,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
must: musts,
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
services: {
|
||||
composite: {
|
||||
size: QUERY_MAX_SIZE,
|
||||
sources: [
|
||||
{
|
||||
serviceName: {
|
||||
terms: {
|
||||
field: 'service.name',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
serviceEnvironment: {
|
||||
terms: {
|
||||
field: 'service.environment',
|
||||
missing_bucket: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
aggs: {
|
||||
last_seen: {
|
||||
max: { field: '@timestamp' },
|
||||
},
|
||||
container_and_hosts: {
|
||||
multi_terms: {
|
||||
terms: [
|
||||
{
|
||||
field: 'host.hostname',
|
||||
},
|
||||
{
|
||||
field: 'container.id',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (afterKey) {
|
||||
dsl.aggs!.services!.composite!.after = afterKey;
|
||||
}
|
||||
|
||||
debug(dsl);
|
||||
|
||||
const esResponse = await client.search(dsl);
|
||||
|
||||
const { after_key: nextKey, buckets = [] } = (esResponse.aggregations?.services || {}) as any;
|
||||
const assets = buckets.reduce((acc: Asset[], bucket: any) => {
|
||||
const {
|
||||
key: { serviceName, serviceEnvironment },
|
||||
container_and_hosts: containerHosts,
|
||||
last_seen: lastSeen,
|
||||
} = bucket;
|
||||
|
||||
if (!serviceName) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
const service: Asset = {
|
||||
'@timestamp': lastSeen.value_as_string,
|
||||
'asset.kind': 'service',
|
||||
'asset.id': serviceName,
|
||||
'asset.ean': `service:${serviceName}`,
|
||||
'asset.references': [],
|
||||
'asset.parents': [],
|
||||
};
|
||||
|
||||
if (serviceEnvironment) {
|
||||
service['service.environment'] = serviceEnvironment;
|
||||
}
|
||||
|
||||
containerHosts.buckets?.forEach((containerBucket: any) => {
|
||||
const [hostname, containerId] = containerBucket.key;
|
||||
if (hostname) {
|
||||
(service['asset.references'] as string[]).push(`host:${hostname}`);
|
||||
}
|
||||
|
||||
if (containerId) {
|
||||
(service['asset.parents'] as string[]).push(`container:${containerId}`);
|
||||
}
|
||||
});
|
||||
|
||||
acc.push(service);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return { assets, afterKey: buckets.length === QUERY_MAX_SIZE ? nextKey : undefined };
|
||||
}
|
|
@ -1,119 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
ClusterPutComponentTemplateRequest,
|
||||
IndicesGetIndexTemplateResponse,
|
||||
IndicesPutIndexTemplateRequest,
|
||||
} from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { ASSETS_INDEX_PREFIX } from '../constants';
|
||||
|
||||
function templateExists(
|
||||
template: IndicesPutIndexTemplateRequest,
|
||||
existing: IndicesGetIndexTemplateResponse | null
|
||||
) {
|
||||
if (existing === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (existing.index_templates.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const checkPatterns = Array.isArray(template.index_patterns)
|
||||
? template.index_patterns
|
||||
: [template.index_patterns];
|
||||
|
||||
return existing.index_templates.some((t) => {
|
||||
const { priority: existingPriority = 0 } = t.index_template;
|
||||
const { priority: incomingPriority = 0 } = template;
|
||||
if (existingPriority !== incomingPriority) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const existingPatterns = Array.isArray(t.index_template.index_patterns)
|
||||
? t.index_template.index_patterns
|
||||
: [t.index_template.index_patterns];
|
||||
|
||||
if (checkPatterns.every((p) => p && existingPatterns.includes(p))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
interface TemplateManagementOptions {
|
||||
esClient: ElasticsearchClient;
|
||||
template: IndicesPutIndexTemplateRequest;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
interface ComponentManagementOptions {
|
||||
esClient: ElasticsearchClient;
|
||||
component: ClusterPutComponentTemplateRequest;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export async function maybeCreateTemplate({
|
||||
esClient,
|
||||
template,
|
||||
logger,
|
||||
}: TemplateManagementOptions) {
|
||||
const pattern = ASSETS_INDEX_PREFIX + '*';
|
||||
template.index_patterns = [pattern];
|
||||
let existing: IndicesGetIndexTemplateResponse | null = null;
|
||||
try {
|
||||
existing = await esClient.indices.getIndexTemplate({ name: template.name });
|
||||
} catch (error: any) {
|
||||
if (error?.name !== 'ResponseError' || error?.statusCode !== 404) {
|
||||
logger.warn(`Asset manager index template lookup failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!templateExists(template, existing)) {
|
||||
await esClient.indices.putIndexTemplate(template);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(`Asset manager index template creation failed: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Asset manager index template is up to date (use debug logging to see what was installed)`
|
||||
);
|
||||
logger.debug(`Asset manager index template: ${JSON.stringify(template)}`);
|
||||
}
|
||||
|
||||
export async function upsertTemplate({ esClient, template, logger }: TemplateManagementOptions) {
|
||||
try {
|
||||
await esClient.indices.putIndexTemplate(template);
|
||||
} catch (error: any) {
|
||||
logger.error(`Error updating asset manager index template: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Asset manager index template is up to date (use debug logging to see what was installed)`
|
||||
);
|
||||
logger.debug(`Asset manager index template: ${JSON.stringify(template)}`);
|
||||
}
|
||||
|
||||
export async function upsertComponent({ esClient, component, logger }: ComponentManagementOptions) {
|
||||
try {
|
||||
await esClient.cluster.putComponentTemplate(component);
|
||||
} catch (error: any) {
|
||||
logger.error(`Error updating asset manager component template: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Asset manager component template is up to date (use debug logging to see what was installed)`
|
||||
);
|
||||
logger.debug(`Asset manager component template: ${JSON.stringify(component)}`);
|
||||
}
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { parseEan } from './parse_ean';
|
||||
|
||||
describe('parseEan function', () => {
|
||||
it('should parse a valid EAN and return the kind and id as separate values', () => {
|
||||
const ean = 'host:some-id-123';
|
||||
const { kind, id } = parseEan(ean);
|
||||
expect(kind).toBe('host');
|
||||
expect(id).toBe('some-id-123');
|
||||
});
|
||||
|
||||
it('should throw an error when the provided EAN does not have enough segments', () => {
|
||||
expect(() => parseEan('invalid-ean')).toThrowError('not a valid EAN');
|
||||
expect(() => parseEan('invalid-ean:')).toThrowError('not a valid EAN');
|
||||
expect(() => parseEan(':invalid-ean')).toThrowError('not a valid EAN');
|
||||
});
|
||||
|
||||
it('should throw an error when the provided EAN has too many segments', () => {
|
||||
const ean = 'host:invalid:segments';
|
||||
expect(() => parseEan(ean)).toThrowError('not a valid EAN');
|
||||
});
|
||||
|
||||
it('should throw an error when the provided EAN includes an unsupported "kind" value', () => {
|
||||
const ean = 'unsupported_kind:some-id-123';
|
||||
expect(() => parseEan(ean)).toThrowError('not a valid EAN');
|
||||
});
|
||||
});
|
|
@ -1,18 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { assetKindRT } from '../../common/types_api';
|
||||
|
||||
export function parseEan(ean: string) {
|
||||
const [kind, id, ...rest] = ean.split(':');
|
||||
|
||||
if (!assetKindRT.is(kind) || !kind || !id || rest.length > 0) {
|
||||
throw new Error(`${ean} is not a valid EAN`);
|
||||
}
|
||||
|
||||
return { kind, id };
|
||||
}
|
|
@ -1,218 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Asset, AssetWithoutTimestamp } from '../../common/types_api';
|
||||
|
||||
// Provide a list of asset EAN values to remove, to simulate disappearing or
|
||||
// appearing assets over time.
|
||||
export function getSampleAssetDocs({
|
||||
baseDateTime = new Date(),
|
||||
excludeEans = [],
|
||||
}: {
|
||||
baseDateTime?: Date;
|
||||
excludeEans?: string[];
|
||||
}): Asset[] {
|
||||
const timestamp = baseDateTime.toISOString();
|
||||
return sampleAssets
|
||||
.filter((asset) => !excludeEans.includes(asset['asset.ean']))
|
||||
.map((asset) => {
|
||||
return {
|
||||
'@timestamp': timestamp,
|
||||
...asset,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const sampleK8sClusters: AssetWithoutTimestamp[] = [
|
||||
{
|
||||
'asset.type': 'k8s.cluster',
|
||||
'asset.kind': 'cluster',
|
||||
'asset.id': 'cluster-001',
|
||||
'asset.name': 'Cluster 001 (AWS EKS)',
|
||||
'asset.ean': 'cluster:cluster-001',
|
||||
'orchestrator.type': 'kubernetes',
|
||||
'orchestrator.cluster.name': 'Cluster 001 (AWS EKS)',
|
||||
'orchestrator.cluster.id': 'cluster-001',
|
||||
'cloud.provider': 'aws',
|
||||
'cloud.region': 'us-east-1',
|
||||
'cloud.service.name': 'eks',
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.cluster',
|
||||
'asset.kind': 'cluster',
|
||||
'asset.id': 'cluster-002',
|
||||
'asset.name': 'Cluster 002 (Azure AKS)',
|
||||
'asset.ean': 'cluster:cluster-002',
|
||||
'orchestrator.type': 'kubernetes',
|
||||
'orchestrator.cluster.name': 'Cluster 002 (Azure AKS)',
|
||||
'orchestrator.cluster.id': 'cluster-002',
|
||||
'cloud.provider': 'azure',
|
||||
'cloud.region': 'eu-west',
|
||||
'cloud.service.name': 'aks',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleK8sNodes: AssetWithoutTimestamp[] = [
|
||||
{
|
||||
'asset.type': 'k8s.node',
|
||||
'asset.kind': 'host',
|
||||
'asset.id': 'node-101',
|
||||
'asset.name': 'k8s-node-101-aws',
|
||||
'asset.ean': 'host:node-101',
|
||||
'asset.parents': ['cluster:cluster-001'],
|
||||
'orchestrator.type': 'kubernetes',
|
||||
'orchestrator.cluster.name': 'Cluster 001 (AWS EKS)',
|
||||
'orchestrator.cluster.id': 'cluster-001',
|
||||
'cloud.provider': 'aws',
|
||||
'cloud.region': 'us-east-1',
|
||||
'cloud.service.name': 'eks',
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.node',
|
||||
'asset.kind': 'host',
|
||||
'asset.id': 'node-102',
|
||||
'asset.name': 'k8s-node-102-aws',
|
||||
'asset.ean': 'host:node-102',
|
||||
'asset.parents': ['cluster:cluster-001'],
|
||||
'orchestrator.type': 'kubernetes',
|
||||
'orchestrator.cluster.name': 'Cluster 001 (AWS EKS)',
|
||||
'orchestrator.cluster.id': 'cluster-001',
|
||||
'cloud.provider': 'aws',
|
||||
'cloud.region': 'us-east-1',
|
||||
'cloud.service.name': 'eks',
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.node',
|
||||
'asset.kind': 'host',
|
||||
'asset.id': 'node-103',
|
||||
'asset.name': 'k8s-node-103-aws',
|
||||
'asset.ean': 'host:node-103',
|
||||
'asset.parents': ['cluster:cluster-001'],
|
||||
'orchestrator.type': 'kubernetes',
|
||||
'orchestrator.cluster.name': 'Cluster 001 (AWS EKS)',
|
||||
'orchestrator.cluster.id': 'cluster-001',
|
||||
'cloud.provider': 'aws',
|
||||
'cloud.region': 'us-east-1',
|
||||
'cloud.service.name': 'eks',
|
||||
},
|
||||
];
|
||||
|
||||
const sampleK8sPods: AssetWithoutTimestamp[] = [
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200xrg1',
|
||||
'asset.name': 'k8s-pod-200xrg1-aws',
|
||||
'asset.ean': 'pod:pod-200xrg1',
|
||||
'asset.parents': ['host:node-101'],
|
||||
'asset.references': ['cluster:cluster-001'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200dfp2',
|
||||
'asset.name': 'k8s-pod-200dfp2-aws',
|
||||
'asset.ean': 'pod:pod-200dfp2',
|
||||
'asset.parents': ['host:node-101'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200wwc3',
|
||||
'asset.name': 'k8s-pod-200wwc3-aws',
|
||||
'asset.ean': 'pod:pod-200wwc3',
|
||||
'asset.parents': ['host:node-101'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200naq4',
|
||||
'asset.name': 'k8s-pod-200naq4-aws',
|
||||
'asset.ean': 'pod:pod-200naq4',
|
||||
'asset.parents': ['host:node-102'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200ohr5',
|
||||
'asset.name': 'k8s-pod-200ohr5-aws',
|
||||
'asset.ean': 'pod:pod-200ohr5',
|
||||
'asset.parents': ['host:node-102'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200yyx6',
|
||||
'asset.name': 'k8s-pod-200yyx6-aws',
|
||||
'asset.ean': 'pod:pod-200yyx6',
|
||||
'asset.parents': ['host:node-103'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200psd7',
|
||||
'asset.name': 'k8s-pod-200psd7-aws',
|
||||
'asset.ean': 'pod:pod-200psd7',
|
||||
'asset.parents': ['host:node-103'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200wmc8',
|
||||
'asset.name': 'k8s-pod-200wmc8-aws',
|
||||
'asset.ean': 'pod:pod-200wmc8',
|
||||
'asset.parents': ['host:node-103'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-200ugg9',
|
||||
'asset.name': 'k8s-pod-200ugg9-aws',
|
||||
'asset.ean': 'pod:pod-200ugg9',
|
||||
'asset.parents': ['host:node-103'],
|
||||
},
|
||||
];
|
||||
|
||||
const sampleCircularReferences: AssetWithoutTimestamp[] = [
|
||||
{
|
||||
'asset.type': 'k8s.node',
|
||||
'asset.kind': 'host',
|
||||
'asset.id': 'node-203',
|
||||
'asset.name': 'k8s-node-203-aws',
|
||||
'asset.ean': 'host:node-203',
|
||||
'orchestrator.type': 'kubernetes',
|
||||
'orchestrator.cluster.name': 'Cluster 001 (AWS EKS)',
|
||||
'orchestrator.cluster.id': 'cluster-001',
|
||||
'cloud.provider': 'aws',
|
||||
'cloud.region': 'us-east-1',
|
||||
'cloud.service.name': 'eks',
|
||||
'asset.references': ['pod:pod-203ugg9', 'pod:pod-203ugg5'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-203ugg5',
|
||||
'asset.name': 'k8s-pod-203ugg5-aws',
|
||||
'asset.ean': 'pod:pod-203ugg5',
|
||||
'asset.references': ['host:node-203'],
|
||||
},
|
||||
{
|
||||
'asset.type': 'k8s.pod',
|
||||
'asset.kind': 'pod',
|
||||
'asset.id': 'pod-203ugg9',
|
||||
'asset.name': 'k8s-pod-203ugg9-aws',
|
||||
'asset.ean': 'pod:pod-203ugg9',
|
||||
'asset.references': ['host:node-203'],
|
||||
},
|
||||
];
|
||||
|
||||
export const sampleAssets: AssetWithoutTimestamp[] = [
|
||||
...sampleK8sClusters,
|
||||
...sampleK8sNodes,
|
||||
...sampleK8sPods,
|
||||
...sampleCircularReferences,
|
||||
];
|
|
@ -1,37 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BulkRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { debug } from '../../common/debug_log';
|
||||
import { Asset } from '../../common/types_api';
|
||||
import { ASSETS_INDEX_PREFIX } from '../constants';
|
||||
import { ElasticsearchAccessorOptions } from '../types';
|
||||
|
||||
interface WriteAssetsOptions extends ElasticsearchAccessorOptions {
|
||||
assetDocs: Asset[];
|
||||
namespace?: string;
|
||||
refresh?: boolean | 'wait_for';
|
||||
}
|
||||
|
||||
export async function writeAssets({
|
||||
elasticsearchClient,
|
||||
assetDocs,
|
||||
namespace = 'default',
|
||||
refresh = false,
|
||||
}: WriteAssetsOptions) {
|
||||
const dsl: BulkRequest<Asset> = {
|
||||
refresh,
|
||||
operations: assetDocs.flatMap((asset) => [
|
||||
{ create: { _index: `${ASSETS_INDEX_PREFIX}-${asset['asset.type']}-${namespace}` } },
|
||||
asset,
|
||||
]),
|
||||
};
|
||||
|
||||
debug('Performing Write Asset Query', '\n\n', JSON.stringify(dsl, null, 2));
|
||||
|
||||
return await elasticsearchClient.bulk<{}>(dsl);
|
||||
}
|
|
@ -1,73 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createRouteValidationFunction } from '@kbn/io-ts-utils';
|
||||
import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import {
|
||||
GetContainerAssetsQueryOptions,
|
||||
getContainerAssetsQueryOptionsRT,
|
||||
} from '../../../common/types_api';
|
||||
import { debug } from '../../../common/debug_log';
|
||||
import { SetupRouteOptions } from '../types';
|
||||
import * as routePaths from '../../../common/constants_routes';
|
||||
import { getClientsFromContext, validateStringAssetFilters } from '../utils';
|
||||
import { AssetsValidationError } from '../../lib/validators/validation_error';
|
||||
|
||||
export function containersRoutes<T extends RequestHandlerContext>({
|
||||
router,
|
||||
server,
|
||||
}: SetupRouteOptions<T>) {
|
||||
const validate = createRouteValidationFunction(getContainerAssetsQueryOptionsRT);
|
||||
router.get<unknown, GetContainerAssetsQueryOptions, unknown>(
|
||||
{
|
||||
path: routePaths.GET_CONTAINERS,
|
||||
validate: {
|
||||
query: (q, res) => {
|
||||
const [invalidResponse, validatedFilters] = validateStringAssetFilters(q, res);
|
||||
if (invalidResponse) {
|
||||
return invalidResponse;
|
||||
}
|
||||
if (validatedFilters) {
|
||||
q.filters = validatedFilters;
|
||||
}
|
||||
return validate(q, res);
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { from = 'now-24h', to = 'now', filters } = req.query || {};
|
||||
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
|
||||
|
||||
try {
|
||||
const response = await server.assetClient.getContainers({
|
||||
from,
|
||||
to,
|
||||
filters, // safe due to route validation, are there better ways to do this?
|
||||
elasticsearchClient,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (error: unknown) {
|
||||
debug('Error while looking up CONTAINER asset records', error);
|
||||
|
||||
if (error instanceof AssetsValidationError) {
|
||||
return res.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: {
|
||||
message: `Error while looking up container asset records - ${error.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: { message: 'Error while looking up container asset records - ' + `${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,70 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createRouteValidationFunction } from '@kbn/io-ts-utils';
|
||||
import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import { GetHostAssetsQueryOptions, getHostAssetsQueryOptionsRT } from '../../../common/types_api';
|
||||
import { debug } from '../../../common/debug_log';
|
||||
import { SetupRouteOptions } from '../types';
|
||||
import * as routePaths from '../../../common/constants_routes';
|
||||
import { getClientsFromContext, validateStringAssetFilters } from '../utils';
|
||||
import { AssetsValidationError } from '../../lib/validators/validation_error';
|
||||
|
||||
export function hostsRoutes<T extends RequestHandlerContext>({
|
||||
router,
|
||||
server,
|
||||
}: SetupRouteOptions<T>) {
|
||||
const validate = createRouteValidationFunction(getHostAssetsQueryOptionsRT);
|
||||
router.get<unknown, GetHostAssetsQueryOptions, unknown>(
|
||||
{
|
||||
path: routePaths.GET_HOSTS,
|
||||
validate: {
|
||||
query: (q, res) => {
|
||||
const [invalidResponse, validatedFilters] = validateStringAssetFilters(q, res);
|
||||
if (invalidResponse) {
|
||||
return invalidResponse;
|
||||
}
|
||||
if (validatedFilters) {
|
||||
q.filters = validatedFilters;
|
||||
}
|
||||
return validate(q, res);
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { from = 'now-24h', to = 'now', filters } = req.query || {};
|
||||
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
|
||||
|
||||
try {
|
||||
const response = await server.assetClient.getHosts({
|
||||
from,
|
||||
to,
|
||||
filters, // safe due to route validation, are there better ways to do this?
|
||||
elasticsearchClient,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (error: unknown) {
|
||||
debug('Error while looking up HOST asset records', error);
|
||||
|
||||
if (error instanceof AssetsValidationError) {
|
||||
return res.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: {
|
||||
message: `Error while looking up host asset records - ${error.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: { message: 'Error while looking up host asset records - ' + `${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,70 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createRouteValidationFunction } from '@kbn/io-ts-utils';
|
||||
import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import { GetAssetsQueryOptions, getAssetsQueryOptionsRT } from '../../../common/types_api';
|
||||
import { debug } from '../../../common/debug_log';
|
||||
import { SetupRouteOptions } from '../types';
|
||||
import * as routePaths from '../../../common/constants_routes';
|
||||
import { getClientsFromContext, validateStringAssetFilters } from '../utils';
|
||||
import { AssetsValidationError } from '../../lib/validators/validation_error';
|
||||
|
||||
export function assetsRoutes<T extends RequestHandlerContext>({
|
||||
router,
|
||||
server,
|
||||
}: SetupRouteOptions<T>) {
|
||||
const validate = createRouteValidationFunction(getAssetsQueryOptionsRT);
|
||||
router.get<unknown, GetAssetsQueryOptions, unknown>(
|
||||
{
|
||||
path: routePaths.GET_ASSETS,
|
||||
validate: {
|
||||
query: (q, res) => {
|
||||
const [invalidResponse, validatedFilters] = validateStringAssetFilters(q, res);
|
||||
if (invalidResponse) {
|
||||
return invalidResponse;
|
||||
}
|
||||
if (validatedFilters) {
|
||||
q.filters = validatedFilters;
|
||||
}
|
||||
return validate(q, res);
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { from = 'now-24h', to = 'now', filters } = req.query || {};
|
||||
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
|
||||
|
||||
try {
|
||||
const response = await server.assetClient.getAssets({
|
||||
from,
|
||||
to,
|
||||
filters,
|
||||
elasticsearchClient,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (error: unknown) {
|
||||
debug('Error while looking up asset records', error);
|
||||
|
||||
if (error instanceof AssetsValidationError) {
|
||||
return res.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: {
|
||||
message: `Error while looking up asset records - ${error.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: { message: 'Error while looking up asset records - ' + `${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,70 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createRouteValidationFunction } from '@kbn/io-ts-utils';
|
||||
import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import { GetPodAssetsQueryOptions, getPodAssetsQueryOptionsRT } from '../../../common/types_api';
|
||||
import { debug } from '../../../common/debug_log';
|
||||
import { SetupRouteOptions } from '../types';
|
||||
import * as routePaths from '../../../common/constants_routes';
|
||||
import { getClientsFromContext, validateStringAssetFilters } from '../utils';
|
||||
import { AssetsValidationError } from '../../lib/validators/validation_error';
|
||||
|
||||
export function podsRoutes<T extends RequestHandlerContext>({
|
||||
router,
|
||||
server,
|
||||
}: SetupRouteOptions<T>) {
|
||||
const validate = createRouteValidationFunction(getPodAssetsQueryOptionsRT);
|
||||
router.get<unknown, GetPodAssetsQueryOptions, unknown>(
|
||||
{
|
||||
path: routePaths.GET_PODS,
|
||||
validate: {
|
||||
query: (q, res) => {
|
||||
const [invalidResponse, validatedFilters] = validateStringAssetFilters(q, res);
|
||||
if (invalidResponse) {
|
||||
return invalidResponse;
|
||||
}
|
||||
if (validatedFilters) {
|
||||
q.filters = validatedFilters;
|
||||
}
|
||||
return validate(q, res);
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { from = 'now-24h', to = 'now', filters } = req.query || {};
|
||||
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
|
||||
|
||||
try {
|
||||
const response = await server.assetClient.getPods({
|
||||
from,
|
||||
to,
|
||||
filters,
|
||||
elasticsearchClient,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (error: unknown) {
|
||||
debug('Error while looking up POD asset records', error);
|
||||
|
||||
if (error instanceof AssetsValidationError) {
|
||||
return res.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: {
|
||||
message: `Error while looking up pod asset records - ${error.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: { message: 'Error while looking up pod asset records - ' + `${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,74 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { createRouteValidationFunction } from '@kbn/io-ts-utils';
|
||||
import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import {
|
||||
GetServiceAssetsQueryOptions,
|
||||
getServiceAssetsQueryOptionsRT,
|
||||
} from '../../../common/types_api';
|
||||
import { debug } from '../../../common/debug_log';
|
||||
import { SetupRouteOptions } from '../types';
|
||||
import * as routePaths from '../../../common/constants_routes';
|
||||
import { getClientsFromContext, validateStringAssetFilters } from '../utils';
|
||||
import { AssetsValidationError } from '../../lib/validators/validation_error';
|
||||
|
||||
export function servicesRoutes<T extends RequestHandlerContext>({
|
||||
router,
|
||||
server,
|
||||
}: SetupRouteOptions<T>) {
|
||||
const validate = createRouteValidationFunction(getServiceAssetsQueryOptionsRT);
|
||||
// GET /assets/services
|
||||
router.get<unknown, GetServiceAssetsQueryOptions, unknown>(
|
||||
{
|
||||
path: routePaths.GET_SERVICES,
|
||||
validate: {
|
||||
query: (q, res) => {
|
||||
const [invalidResponse, validatedFilters] = validateStringAssetFilters(q, res);
|
||||
if (invalidResponse) {
|
||||
return invalidResponse;
|
||||
}
|
||||
if (validatedFilters) {
|
||||
q.filters = validatedFilters;
|
||||
}
|
||||
return validate(q, res);
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { from = 'now-24h', to = 'now', filters } = req.query || {};
|
||||
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
|
||||
try {
|
||||
const response = await server.assetClient.getServices({
|
||||
from,
|
||||
to,
|
||||
filters,
|
||||
elasticsearchClient,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (error: unknown) {
|
||||
debug('Error while looking up SERVICE asset records', error);
|
||||
|
||||
if (error instanceof AssetsValidationError) {
|
||||
return res.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: {
|
||||
message: `Error while looking up service asset records - ${error.message}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: { message: 'Error while looking up service asset records - ' + `${error}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,143 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { RequestHandlerContext } from '@kbn/core/server';
|
||||
import { ASSET_MANAGER_API_BASE } from '../../common/constants_routes';
|
||||
import { getSampleAssetDocs, sampleAssets } from '../lib/sample_assets';
|
||||
import { writeAssets } from '../lib/write_assets';
|
||||
import { SetupRouteOptions } from './types';
|
||||
import { getClientsFromContext } from './utils';
|
||||
|
||||
export type WriteSamplesPostBody = {
|
||||
baseDateTime?: string | number;
|
||||
excludeEans?: string[];
|
||||
refresh?: boolean | 'wait_for';
|
||||
} | null;
|
||||
|
||||
export function sampleAssetsRoutes<T extends RequestHandlerContext>({
|
||||
router,
|
||||
}: SetupRouteOptions<T>) {
|
||||
const SAMPLE_ASSETS_API_PATH = `${ASSET_MANAGER_API_BASE}/assets/sample`;
|
||||
|
||||
// GET sample assets
|
||||
router.get<unknown, unknown, unknown>(
|
||||
{
|
||||
path: SAMPLE_ASSETS_API_PATH,
|
||||
validate: {},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
return res.ok({ body: { results: sampleAssets } });
|
||||
}
|
||||
);
|
||||
|
||||
// POST sample assets
|
||||
router.post<unknown, unknown, WriteSamplesPostBody>(
|
||||
{
|
||||
path: SAMPLE_ASSETS_API_PATH,
|
||||
validate: {
|
||||
body: schema.nullable(
|
||||
schema.object({
|
||||
baseDateTime: schema.maybe(
|
||||
schema.oneOf<string, number>([schema.string(), schema.number()])
|
||||
),
|
||||
excludeEans: schema.maybe(schema.arrayOf(schema.string())),
|
||||
refresh: schema.maybe(schema.oneOf([schema.boolean(), schema.literal('wait_for')])),
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { baseDateTime, excludeEans, refresh } = req.body || {};
|
||||
const parsed = baseDateTime === undefined ? undefined : new Date(baseDateTime);
|
||||
if (parsed?.toString() === 'Invalid Date') {
|
||||
return res.customError({
|
||||
statusCode: 400,
|
||||
body: {
|
||||
message: `${baseDateTime} is not a valid date time value`,
|
||||
},
|
||||
});
|
||||
}
|
||||
const { elasticsearchClient } = await getClientsFromContext(context);
|
||||
const assetDocs = getSampleAssetDocs({ baseDateTime: parsed, excludeEans });
|
||||
|
||||
try {
|
||||
const response = await writeAssets({
|
||||
elasticsearchClient,
|
||||
assetDocs,
|
||||
namespace: 'sample_data',
|
||||
refresh,
|
||||
});
|
||||
|
||||
if (response.errors) {
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: {
|
||||
message: JSON.stringify(response.errors),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return res.ok({ body: response });
|
||||
} catch (error: any) {
|
||||
return res.customError({
|
||||
statusCode: 500,
|
||||
body: {
|
||||
message: error.message || 'unknown error occurred while creating sample assets',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// DELETE all sample assets
|
||||
router.delete(
|
||||
{
|
||||
path: SAMPLE_ASSETS_API_PATH,
|
||||
validate: {},
|
||||
},
|
||||
async (context, req, res) => {
|
||||
const { elasticsearchClient } = await getClientsFromContext(context);
|
||||
|
||||
const sampleDataStreams = await elasticsearchClient.indices.getDataStream({
|
||||
name: 'assets-*-sample_data',
|
||||
expand_wildcards: 'all',
|
||||
});
|
||||
|
||||
const deletedDataStreams: string[] = [];
|
||||
let errorWhileDeleting: string | null = null;
|
||||
const dataStreamsToDelete = sampleDataStreams.data_streams.map((ds) => ds.name);
|
||||
|
||||
for (let i = 0; i < dataStreamsToDelete.length; i++) {
|
||||
const dsName = dataStreamsToDelete[i];
|
||||
try {
|
||||
await elasticsearchClient.indices.deleteDataStream({ name: dsName });
|
||||
deletedDataStreams.push(dsName);
|
||||
} catch (error: any) {
|
||||
errorWhileDeleting =
|
||||
typeof error.message === 'string'
|
||||
? error.message
|
||||
: `Unknown error occurred while deleting sample data streams, at data stream name: ${dsName}`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!errorWhileDeleting && deletedDataStreams.length === dataStreamsToDelete.length) {
|
||||
return res.ok({ body: { deleted: deletedDataStreams } });
|
||||
} else {
|
||||
return res.custom({
|
||||
statusCode: 500,
|
||||
body: {
|
||||
message: ['Not all found data streams were deleted', errorWhileDeleting].join(' - '),
|
||||
deleted: deletedDataStreams,
|
||||
matching: dataStreamsToDelete,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,46 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
RequestHandlerContext,
|
||||
RouteValidationError,
|
||||
RouteValidationResultFactory,
|
||||
} from '@kbn/core/server';
|
||||
import { AssetFilters, assetFiltersSingleKindRT } from '../../common/types_api';
|
||||
|
||||
export async function getClientsFromContext<T extends RequestHandlerContext>(context: T) {
|
||||
const coreContext = await context.core;
|
||||
|
||||
return {
|
||||
coreContext,
|
||||
elasticsearchClient: coreContext.elasticsearch.client.asCurrentUser,
|
||||
savedObjectsClient: coreContext.savedObjects.client,
|
||||
};
|
||||
}
|
||||
|
||||
type ValidateStringAssetFiltersReturn =
|
||||
| [{ error: RouteValidationError }]
|
||||
| [null, AssetFilters | undefined];
|
||||
|
||||
export function validateStringAssetFilters(
|
||||
q: any,
|
||||
res: RouteValidationResultFactory
|
||||
): ValidateStringAssetFiltersReturn {
|
||||
if (typeof q.stringFilters === 'string') {
|
||||
try {
|
||||
const parsedFilters = JSON.parse(q.stringFilters);
|
||||
if (assetFiltersSingleKindRT.is(parsedFilters)) {
|
||||
return [null, parsedFilters];
|
||||
} else {
|
||||
return [res.badRequest(new Error(`Invalid asset filters - ${q.filters}`))];
|
||||
}
|
||||
} catch (err: any) {
|
||||
return [res.badRequest(err)];
|
||||
}
|
||||
}
|
||||
return [null, undefined];
|
||||
}
|
|
@ -1,44 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { ASSETS_INDEX_PREFIX } from '../constants';
|
||||
|
||||
export const assetsIndexTemplateConfig: IndicesPutIndexTemplateRequest = {
|
||||
name: 'assets',
|
||||
priority: 100,
|
||||
data_stream: {},
|
||||
index_patterns: [`${ASSETS_INDEX_PREFIX}*`],
|
||||
template: {
|
||||
settings: {},
|
||||
mappings: {
|
||||
dynamic_templates: [
|
||||
{
|
||||
strings_as_keywords: {
|
||||
mapping: {
|
||||
ignore_above: 1024,
|
||||
type: 'keyword',
|
||||
},
|
||||
match_mapping_type: 'string',
|
||||
},
|
||||
},
|
||||
],
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
asset: {
|
||||
type: 'object',
|
||||
// subobjects appears to not exist in the types, but is a valid ES mapping option
|
||||
// see: https://www.elastic.co/guide/en/elasticsearch/reference/master/subobjects.html
|
||||
// @ts-ignore
|
||||
subobjects: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,46 +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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
// Helper function allows test to verify error was thrown,
|
||||
// verify error is of the right class type, and error has
|
||||
|
||||
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { GetApmIndicesMethod } from './lib/asset_client_types';
|
||||
import { AssetsValidationError } from './lib/validators/validation_error';
|
||||
|
||||
// the expected metadata such as statusCode on it
|
||||
export function expectToThrowValidationErrorWithStatusCode(
|
||||
testFn: () => Promise<any>,
|
||||
expectedError: Partial<AssetsValidationError> = {}
|
||||
) {
|
||||
return expect(async () => {
|
||||
try {
|
||||
return await testFn();
|
||||
} catch (error: any) {
|
||||
if (error instanceof AssetsValidationError) {
|
||||
if (expectedError.statusCode) {
|
||||
expect(error.statusCode).toEqual(expectedError.statusCode);
|
||||
}
|
||||
if (expectedError.message) {
|
||||
expect(error.message).toEqual(expect.stringContaining(expectedError.message));
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}).rejects.toThrow(AssetsValidationError);
|
||||
}
|
||||
|
||||
export function createGetApmIndicesMock(): jest.Mocked<GetApmIndicesMethod> {
|
||||
return jest.fn(async (client: SavedObjectsClientContract) => ({
|
||||
transaction: 'apm-mock-transaction-indices',
|
||||
span: 'apm-mock-span-indices',
|
||||
error: 'apm-mock-error-indices',
|
||||
metric: 'apm-mock-metric-indices',
|
||||
onboarding: 'apm-mock-onboarding-indices',
|
||||
sourcemap: 'apm-mock-sourcemap-indices',
|
||||
}));
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# Entity Manager Plugin
|
||||
|
||||
This plugin provides access to observed asset data, such as information about hosts, pods, containers, services, and more.
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
|
||||
export const configSchema = schema.object({});
|
||||
|
||||
export type EntityManagerConfig = TypeOf<typeof configSchema>;
|
||||
|
||||
/**
|
||||
* The following map is passed to the server plugin setup under the
|
||||
* exposeToBrowser: option, and controls which of the above config
|
||||
* keys are allow-listed to be available in the browser config.
|
||||
*
|
||||
* NOTE: anything exposed here will be visible in the UI dev tools,
|
||||
* and therefore MUST NOT be anything that is sensitive information!
|
||||
*/
|
||||
export const exposeToBrowserConfig = {} as const;
|
||||
|
||||
type ValidKeys = keyof {
|
||||
[K in keyof typeof exposeToBrowserConfig as typeof exposeToBrowserConfig[K] extends true
|
||||
? K
|
||||
: never]: true;
|
||||
};
|
||||
|
||||
export type EntityManagerPublicConfig = Pick<EntityManagerConfig, ValidKeys>;
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
|
||||
/**
|
||||
* Managed entities enablement
|
||||
*/
|
||||
export const managedEntityEnabledResponseRT = rt.type({
|
||||
enabled: rt.boolean,
|
||||
reason: rt.string,
|
||||
});
|
||||
export type ManagedEntityEnabledResponse = rt.TypeOf<typeof managedEntityEnabledResponseRT>;
|
||||
|
||||
export const managedEntityResponseBase = rt.type({
|
||||
success: rt.boolean,
|
||||
reason: rt.string,
|
||||
message: rt.string,
|
||||
});
|
||||
export type EnableManagedEntityResponse = rt.TypeOf<typeof managedEntityResponseBase>;
|
||||
export type DisableManagedEntityResponse = rt.TypeOf<typeof managedEntityResponseBase>;
|
|
@ -8,11 +8,11 @@
|
|||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/observability_solution/asset_manager'],
|
||||
roots: ['<rootDir>/x-pack/plugins/observability_solution/entity_manager'],
|
||||
coverageDirectory:
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/observability_solution/asset_manager',
|
||||
'<rootDir>/target/kibana-coverage/jest/x-pack/plugins/observability_solution/entity_manager',
|
||||
coverageReporters: ['text', 'html'],
|
||||
collectCoverageFrom: [
|
||||
'<rootDir>/x-pack/plugins/observability_solution/asset_manager/{common,public,server}/**/*.{js,ts,tsx}',
|
||||
'<rootDir>/x-pack/plugins/observability_solution/entity_manager/{common,public,server}/**/*.{js,ts,tsx}',
|
||||
],
|
||||
};
|
|
@ -1,20 +1,18 @@
|
|||
{
|
||||
"type": "plugin",
|
||||
"id": "@kbn/assetManager-plugin",
|
||||
"id": "@kbn/entityManager-plugin",
|
||||
"owner": "@elastic/obs-knowledge-team",
|
||||
"description": "Asset manager plugin for entity assets (inventory, topology, etc)",
|
||||
"description": "Entity manager plugin for entity assets (inventory, topology, etc)",
|
||||
"plugin": {
|
||||
"id": "assetManager",
|
||||
"id": "entityManager",
|
||||
"configPath": [
|
||||
"xpack",
|
||||
"assetManager"
|
||||
"entityManager"
|
||||
],
|
||||
"optionalPlugins": [
|
||||
"spaces"
|
||||
],
|
||||
"requiredPlugins": [
|
||||
"apmDataAccess",
|
||||
"metricsDataAccess",
|
||||
"security",
|
||||
"encryptedSavedObjects",
|
||||
],
|
|
@ -7,14 +7,14 @@
|
|||
|
||||
import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
|
||||
import { Plugin } from './plugin';
|
||||
import { AssetManagerPublicPluginSetup, AssetManagerPublicPluginStart } from './types';
|
||||
import { EntityManagerPublicPluginSetup, EntityManagerPublicPluginStart } from './types';
|
||||
|
||||
export const plugin: PluginInitializer<
|
||||
AssetManagerPublicPluginSetup | undefined,
|
||||
AssetManagerPublicPluginStart | undefined
|
||||
EntityManagerPublicPluginSetup | undefined,
|
||||
EntityManagerPublicPluginStart | undefined
|
||||
> = (context: PluginInitializerContext) => {
|
||||
return new Plugin(context);
|
||||
};
|
||||
|
||||
export type { AssetManagerPublicPluginSetup, AssetManagerPublicPluginStart };
|
||||
export type AssetManagerAppId = 'assetManager';
|
||||
export type { EntityManagerPublicPluginSetup, EntityManagerPublicPluginStart };
|
||||
export type EntityManagerAppId = 'entityManager';
|
|
@ -8,13 +8,12 @@
|
|||
import { CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/public';
|
||||
import { Logger } from '@kbn/logging';
|
||||
|
||||
import { AssetManagerPluginClass } from './types';
|
||||
import { PublicAssetsClient } from './lib/public_assets_client';
|
||||
import type { AssetManagerPublicConfig } from '../common/config';
|
||||
import { EntityManagerPluginClass } from './types';
|
||||
import type { EntityManagerPublicConfig } from '../common/config';
|
||||
import { EntityClient } from './lib/entity_client';
|
||||
|
||||
export class Plugin implements AssetManagerPluginClass {
|
||||
public config: AssetManagerPublicConfig;
|
||||
export class Plugin implements EntityManagerPluginClass {
|
||||
public config: EntityManagerPublicConfig;
|
||||
public logger: Logger;
|
||||
|
||||
constructor(context: PluginInitializerContext<{}>) {
|
||||
|
@ -23,32 +22,15 @@ export class Plugin implements AssetManagerPluginClass {
|
|||
}
|
||||
|
||||
setup(core: CoreSetup) {
|
||||
// Check for config value and bail out if not "alpha-enabled"
|
||||
if (!this.config.alphaEnabled) {
|
||||
this.logger.debug('Public is NOT enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug('Public is enabled');
|
||||
|
||||
const publicAssetsClient = new PublicAssetsClient(core.http);
|
||||
const entityClient = new EntityClient(core.http);
|
||||
return {
|
||||
publicAssetsClient,
|
||||
entityClient,
|
||||
};
|
||||
}
|
||||
|
||||
start(core: CoreStart) {
|
||||
// Check for config value and bail out if not "alpha-enabled"
|
||||
if (!this.config.alphaEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const publicAssetsClient = new PublicAssetsClient(core.http);
|
||||
const entityClient = new EntityClient(core.http);
|
||||
return {
|
||||
publicAssetsClient,
|
||||
entityClient,
|
||||
};
|
||||
}
|
|
@ -5,33 +5,25 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type { Plugin as PluginClass } from '@kbn/core/public';
|
||||
import { GetHostsOptionsPublic } from '../common/types_client';
|
||||
import {
|
||||
DisableManagedEntityResponse,
|
||||
EnableManagedEntityResponse,
|
||||
GetHostAssetsResponse,
|
||||
ManagedEntityEnabledResponse,
|
||||
} from '../common/types_api';
|
||||
|
||||
export interface AssetManagerPublicPluginSetup {
|
||||
publicAssetsClient: IPublicAssetsClient;
|
||||
export interface EntityManagerPublicPluginSetup {
|
||||
entityClient: IEntityClient;
|
||||
}
|
||||
|
||||
export interface AssetManagerPublicPluginStart {
|
||||
publicAssetsClient: IPublicAssetsClient;
|
||||
export interface EntityManagerPublicPluginStart {
|
||||
entityClient: IEntityClient;
|
||||
}
|
||||
|
||||
export type AssetManagerPluginClass = PluginClass<
|
||||
AssetManagerPublicPluginSetup | undefined,
|
||||
AssetManagerPublicPluginStart | undefined
|
||||
export type EntityManagerPluginClass = PluginClass<
|
||||
EntityManagerPublicPluginSetup | undefined,
|
||||
EntityManagerPublicPluginStart | undefined
|
||||
>;
|
||||
|
||||
export interface IPublicAssetsClient {
|
||||
getHosts: (options: GetHostsOptionsPublic) => Promise<GetHostAssetsResponse>;
|
||||
}
|
||||
|
||||
export interface IEntityClient {
|
||||
isManagedEntityDiscoveryEnabled: () => Promise<ManagedEntityEnabledResponse>;
|
||||
enableManagedEntityDiscovery: () => Promise<EnableManagedEntityResponse>;
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from '@kbn/core-plugins-server';
|
||||
import { EntityManagerConfig } from '../common/config';
|
||||
import { EntityManagerServerPluginSetup, EntityManagerServerPluginStart, config } from './plugin';
|
||||
|
||||
export type { EntityManagerConfig, EntityManagerServerPluginSetup, EntityManagerServerPluginStart };
|
||||
export { config };
|
||||
|
||||
export const plugin = async (context: PluginInitializerContext<EntityManagerConfig>) => {
|
||||
const { EntityManagerServerPlugin } = await import('./plugin');
|
||||
return new EntityManagerServerPlugin(context);
|
||||
};
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { KibanaRequest } from '@kbn/core-http-server';
|
||||
import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request';
|
||||
import { AssetManagerServerSetup } from '../../../types';
|
||||
import { EntityManagerServerSetup } from '../../../types';
|
||||
import { canRunEntityDiscovery, requiredRunTimePrivileges } from '../privileges';
|
||||
|
||||
export interface EntityDiscoveryAPIKey {
|
||||
|
@ -17,13 +17,13 @@ export interface EntityDiscoveryAPIKey {
|
|||
}
|
||||
|
||||
export const checkIfAPIKeysAreEnabled = async (
|
||||
server: AssetManagerServerSetup
|
||||
server: EntityManagerServerSetup
|
||||
): Promise<boolean> => {
|
||||
return await server.security.authc.apiKeys.areAPIKeysEnabled();
|
||||
};
|
||||
|
||||
export const checkIfEntityDiscoveryAPIKeyIsValid = async (
|
||||
server: AssetManagerServerSetup,
|
||||
server: EntityManagerServerSetup,
|
||||
apiKey: EntityDiscoveryAPIKey
|
||||
): Promise<boolean> => {
|
||||
server.logger.debug('validating API key against authentication service');
|
||||
|
@ -49,7 +49,7 @@ export const checkIfEntityDiscoveryAPIKeyIsValid = async (
|
|||
};
|
||||
|
||||
export const generateEntityDiscoveryAPIKey = async (
|
||||
server: AssetManagerServerSetup,
|
||||
server: EntityManagerServerSetup,
|
||||
req: KibanaRequest
|
||||
): Promise<EntityDiscoveryAPIKey | undefined> => {
|
||||
const apiKey = await server.security.authc.apiKeys.grantAsInternalUser(req, {
|
|
@ -7,18 +7,18 @@
|
|||
|
||||
import { SavedObjectsErrorHelpers, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { EntityDiscoveryApiKeyType } from '../../../saved_objects';
|
||||
import { AssetManagerServerSetup } from '../../../types';
|
||||
import { EntityManagerServerSetup } from '../../../types';
|
||||
import { EntityDiscoveryAPIKey } from './api_key';
|
||||
|
||||
const ENTITY_DISCOVERY_API_KEY_SO_ID = '19540C97-E35C-485B-8566-FB86EC8455E4';
|
||||
|
||||
const getEncryptedSOClient = (server: AssetManagerServerSetup) => {
|
||||
const getEncryptedSOClient = (server: EntityManagerServerSetup) => {
|
||||
return server.encryptedSavedObjects.getClient({
|
||||
includedHiddenTypes: [EntityDiscoveryApiKeyType.name],
|
||||
});
|
||||
};
|
||||
|
||||
export const readEntityDiscoveryAPIKey = async (server: AssetManagerServerSetup) => {
|
||||
export const readEntityDiscoveryAPIKey = async (server: EntityManagerServerSetup) => {
|
||||
try {
|
||||
const soClient = getEncryptedSOClient(server);
|
||||
const obj = await soClient.getDecryptedAsInternalUser<EntityDiscoveryAPIKey>(
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue