[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:
Kevin Lacabane 2024-06-26 14:25:32 +02:00 committed by GitHub
parent 34f76adc75
commit a493e4075b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
152 changed files with 302 additions and 4793 deletions

View file

@ -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
View file

@ -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

View file

@ -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": []
}
}

View file

@ -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}/>

View file

@ -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 |

View file

@ -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 |

View file

@ -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 @@
}
]
}
}
}

View file

@ -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": []
}
}
}

View file

@ -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 |

View file

@ -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": []
}
}
}

View file

@ -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.

View file

@ -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",

View file

@ -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",

View file

@ -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": {

View file

@ -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

View file

@ -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",

View file

@ -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',

View file

@ -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",

View file

@ -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)',

View file

@ -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"],

View file

@ -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).

View file

@ -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>;

View file

@ -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');

View file

@ -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>;

View file

@ -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>;

View file

@ -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');
});
});
});

View file

@ -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;
}
}

View file

@ -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';

View file

@ -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);
};

View file

@ -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 }
);
});
});

View file

@ -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,
};
}

View file

@ -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 }
);
});
});

View file

@ -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,
};
}

View file

@ -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 }
);
});
});

View file

@ -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,
};
}

View file

@ -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 };
}

View file

@ -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 }
);
});
});
});

View file

@ -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']) };
}
}

View file

@ -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;

View file

@ -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 };
}

View file

@ -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 };
}

View file

@ -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';

View file

@ -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 };
}

View file

@ -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 };
}

View file

@ -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)}`);
}

View file

@ -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');
});
});

View file

@ -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 };
}

View file

@ -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,
];

View file

@ -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);
}

View file

@ -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}` },
});
}
}
);
}

View file

@ -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}` },
});
}
}
);
}

View file

@ -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}` },
});
}
}
);
}

View file

@ -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}` },
});
}
}
);
}

View file

@ -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}` },
});
}
}
);
}

View file

@ -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,
},
});
}
}
);
}

View file

@ -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];
}

View file

@ -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,
},
},
},
},
};

View file

@ -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',
}));
}

View file

@ -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.

View file

@ -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>;

View file

@ -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>;

View file

@ -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}',
],
};

View file

@ -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",
],

View file

@ -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';

View file

@ -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,
};
}

View file

@ -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>;

View file

@ -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);
};

View file

@ -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, {

View file

@ -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