[Asset Manager] Creates baseline public asset client for use in public plugins (#167191)

Closes #167075 

## Summary

Adds a public asset client available in the `setup` lifecycle hook for
plugins that depend on this one. `getHosts` is the only method available
on this client for now.

TODO, before merge:
- [x] Add docs for the server client
- [x] Add docs for the public client
- [x] Remove REST docs from plugin docs, not needed
- [x] Add unit tests for public client


### Testing this PR

One way of testing this new client is to apply the attached
test-assets.patch file locally, adjust the date range in the getHosts
query that is added in the infra plugin, and then start Kibana and
navigate to the infra app. You should see print out in the browser
console.


[test-assets.patch](12718693/test-assets.patch)

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jason Rhodes 2023-09-28 16:26:24 -04:00 committed by GitHub
parent 424dec613f
commit 859ae9e50d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 753 additions and 297 deletions

View file

@ -451,8 +451,7 @@ The plugin exposes the static DefaultEditorController class to consume.
|{kib-repo}blob/{branch}/x-pack/plugins/asset_manager/README.md[assetManager]
|This plugin provides access to the asset data stored in assets-* indices, primarily
for inventory and topology purposes.
|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/banners/README.md[banners]

View file

@ -4,6 +4,7 @@ pageLoadAssetSize:
aiops: 10000
alerting: 106936
apm: 64385
assetManager: 25000
banners: 17946
bfetch: 22837
canvas: 1066647

View file

@ -197,6 +197,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.apm.featureFlags.sourcemapApiAvailable (any)',
'xpack.apm.featureFlags.storageExplorerAvailable (any)',
'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

@ -1,39 +1,13 @@
# Asset Manager Plugin
This plugin provides access to the asset data stored in assets-\* indices, primarily
for inventory and topology purposes.
This plugin provides access to observed asset data, such as information about hosts, pods, containers, services, and more.
## Documentation
See [docs for the provided APIs in the docs folder](./docs/index.md).
### User Docs
## Running Tests
For those interested in making use of the APIs provided by this plugin, see [our API docs](./docs/api.md).
There are integration tests for the endpoints implemented thus far as well as for
the sample data tests. There is also a small set of tests meant to ensure that the
plugin is not doing anything without the proper config value in place to enable
the plugin fully. For more on enabling the plugin, see [the docs page](./docs/index.md).
### Developer Docs
The "not enabled" tests are run by default in CI. To run them manually, do the following:
```shell
$ node scripts/functional_tests_server --config x-pack/test/api_integration/apis/asset_manager/config_when_disabled.ts
$ node scripts/functional_test_runner --config=x-pack/test/api_integration/apis/asset_manager/config_when_disabled.ts
```
The "enabled" tests are NOT run by CI yet, to prevent blocking Kibana development for a
test failure in this alpha, tech preview plugin. They will be moved into the right place
to make them run for CI before the plugin is enabled by default. To run them manually:
```shell
$ node scripts/functional_tests_server --config x-pack/test/api_integration/apis/asset_manager/config.ts
$ node scripts/functional_test_runner --config=x-pack/test/api_integration/apis/asset_manager/config.ts
```
## Using Sample Data
This plugin comes with a full "working set" of sample asset documents, meant
to provide enough data in the correct schema format so that all of the API
endpoints return expected values.
To create the sample data, follow [the instructions in the REST API docs](./docs/index.md#sample-data).
For those working on this plugin directly and developing it, please see [our development docs](./docs/development.md).

View file

@ -0,0 +1,56 @@
/*
* 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 }
),
// Choose an explicit source for asset queries.
// NOTE: This will eventually need to be able to cleverly switch
// between these values based on the availability of data in the
// indices, and possibly for each asset kind/type value.
// For now, we set this explicitly.
lockedSource: schema.oneOf([schema.literal('assets'), schema.literal('signals')], {
defaultValue: 'signals',
}),
});
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

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

View file

@ -6,77 +6,111 @@
*/
import * as rt from 'io-ts';
import {
dateRt,
inRangeFromStringRt,
datemathStringRt,
createLiteralValueFromUndefinedRT,
} from '@kbn/io-ts-utils';
export const assetTypeRT = rt.union([
rt.literal('k8s.pod'),
rt.literal('k8s.cluster'),
rt.literal('k8s.node'),
]);
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.union([
rt.literal('cluster'),
rt.literal('host'),
rt.literal('pod'),
rt.literal('container'),
rt.literal('service'),
rt.literal('alert'),
]);
export const assetKindRT = rt.keyof({
cluster: null,
host: null,
pod: null,
container: null,
service: null,
alert: null,
});
export type AssetKind = rt.TypeOf<typeof assetKindRT>;
export type AssetStatus =
| 'CREATING'
| 'ACTIVE'
| 'DELETING'
| 'FAILED'
| 'UPDATING'
| 'PENDING'
| 'UNKNOWN';
export type CloudProviderName = 'aws' | 'gcp' | 'azure' | 'other' | 'unknown' | 'none';
export const assetStatusRT = rt.keyof({
CREATING: null,
ACTIVE: null,
DELETING: null,
FAILED: null,
UPDATING: null,
PENDING: null,
UNKNOWN: null,
});
interface WithTimestamp {
'@timestamp': string;
}
export interface ECSDocument extends WithTimestamp {
'kubernetes.namespace'?: string;
'kubernetes.pod.name'?: string;
'kubernetes.pod.uid'?: string;
'kubernetes.pod.start_time'?: Date;
'kubernetes.node.name'?: string;
'kubernetes.node.start_time'?: Date;
export type AssetStatus = rt.TypeOf<typeof assetStatusRT>;
'orchestrator.api_version'?: string;
'orchestrator.namespace'?: string;
'orchestrator.organization'?: string;
'orchestrator.type'?: string;
'orchestrator.cluster.id'?: string;
'orchestrator.cluster.name'?: string;
'orchestrator.cluster.url'?: string;
'orchestrator.cluster.version'?: string;
// 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,
});
'cloud.provider'?: CloudProviderName;
'cloud.instance.id'?: string;
'cloud.region'?: string;
'cloud.service.name'?: string;
export type CloudProviderName = rt.TypeOf<typeof cloudProviderNameRT>;
'service.environment'?: string;
}
const withTimestampRT = rt.type({
'@timestamp': rt.string,
});
export interface Asset extends ECSDocument {
'asset.collection_version'?: string;
'asset.ean': string;
'asset.id': string;
'asset.kind': AssetKind;
'asset.name'?: string;
'asset.type'?: AssetType;
'asset.status'?: AssetStatus;
'asset.parents'?: string | string[];
'asset.children'?: string | string[];
'asset.references'?: string | string[];
'asset.namespace'?: 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'>;
@ -156,3 +190,22 @@ 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]);
export const getHostAssetsQueryOptionsRT = rt.exact(
rt.partial({
from: assetDateRT,
to: assetDateRT,
size: sizeRT,
})
);
export type GetHostAssetsQueryOptions = rt.TypeOf<typeof getHostAssetsQueryOptionsRT>;
export const getHostAssetsResponseRT = rt.type({
hosts: rt.array(assetRT),
});
export type GetHostAssetsResponse = rt.TypeOf<typeof getHostAssetsResponseRT>;

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export interface GetHostsOptionsPublic {
from: string;
to: string;
}
export interface GetServicesOptionsPublic {
from: string;
to: string;
parent?: string;
}

View file

@ -0,0 +1,187 @@
# Asset Manager API Documentation
## Plugin configuration
This plugin is NOT fully enabled by default, even though it's always enabled
by Kibana's definition of "enabled". However, without the following configuration,
it will bail before it sets up any routes or returns anything from its
start, setup, or stop hooks.
To fully enable the plugin, set the following config values in your kibana.yml file:
```yaml
xpack.assetManager:
alphaEnabled: true
```
## Depending on an asset client in your packages
If you're creating a shared UI component or tool that needs to access asset data, you
can create that code in a stateless Kibana package that can itself be imported into
any Kibana plugin without any dependency restrictions. To gain access to the asset data,
this component or tool can require the appropriate asset client to be passed in.
TODO: need to move main client types to a package so that they can be depended on by
other packages that require an injected asset client. Then we can list that package name
here and explain how to use those types in a package.
## Client APIs
This plugin provides asset clients for Kibana server and public usage. The differences between these
two clients are described below in their sections, while the methods for both APIs are described
in the Client Methods section.
These clients are set up in the following way. For a given "methodA":
```
publicMethodA(...options: MethodAPublicOptions)
-> browser client calls corresponding REST API method with MethodAPublicOptions
-> REST API handler calls corresponding serverMethodA
-> serverMethodA requires MethodAPublicOptions & AssetClientDependencies, and it also
injects some internal dependencies from the plugin's config on your behalf
```
The public and server clientss are both accessible to plugin dependants, but the REST API is NOT.
### Required dependency setup
To use either client, you must first add "assetManager" to your `"requiredDependencies"` array
in your plugin's kibana.jsonc file.
TECH PREVIEW NOTE: While this plugin is in "tech preview", in both the server and public clients,
the provided plugin dependencies can be undefined for this plugin if the proper configuration
has not been set (see above). For that reason, the types will force you to guard against this
undefined scenario. Once the tech preview gating is removed, this will no longer be the case.
### Server client usage
In your plugin's `setup` method, you can gain access to the client from the injected `plugins` map.
Make sure you import the `AssetManagerServerPluginSetup` type from the plugin's server
directory and add it to your own SetupPlugins type, as seen below.
```ts
import { AssetManagerServerPluginSetup } from '@kbn/assetManager-plugin/server';
interface MyPluginSetupDeps {
assetManager: AssetManagerServerPluginSetup;
}
class MyPlugin {
setup(core: CoreSetup, plugins: MyPluginSetupDeps) {
// assetClient is found on plugins.assetManager.assetClient
setupRoutes(router, plugins);
}
}
```
To use the server client in your server routes, you can use something like this:
```ts
export function setupRoutes(router: IRouter, plugins: MyPluginDeps) {
router.get<unknown, MyRouteOptions, unknown>(
{
path: '/my/path',
validate: {},
},
async (context, req, res) => {
// handle route
// optionally, use asset client
// NOTE: see below for important info on required server client args
const hosts = await plugins.assetManager.assetClient.getHosts();
}
);
}
```
#### Required parameters for server client methods
All methods called via the server client require some core Kibana clients to be passed in,
so that they are pulled from the request context and properly scoped. If the asset manager
plugin provided these clients internally, they would not be scoped to the user that made
the API request, so they are required arguments for every server client method.
_Note: These required arguments are referred to as `AssetClientDependencies`, which can be
seen in the [the server types file](../server/types.ts)._
For example:
```ts
router.get<unknown, MyRouteOptions, unknown>(
{
path: '/my/path',
validate: {},
},
async (context, req, res) => {
// to use server asset client, you must get the following clients
// from the request context and pass them to the client method
// alongside whatever "public" arguments that method defines
const coreContext = await context.core;
const hostsOptions: PublicGetHostsOptions = {}; // these will be different for each method
const hosts = await plugins.assetManager.assetClient.getHosts({
...hostsOptions,
elasticsearchClient: coreContext.elasticsearch.client.asCurrentUser,
savedObjectsClient: coreContext.savedObjects.client,
});
}
);
```
### Public client usage
You should grab the public client in the same way as the server one, via the plugin dependencies
in your `setup` lifecycle.
```ts
import { AssetManagerPublicPluginStart } from '@kbn/assetManager-plugin/public';
interface MyPluginStartDeps {
assetManager: AssetManagerPublicPluginStart;
}
class MyPlugin {
setup(core: CoreSetup) {
core.application.register({
id: 'my-other-plugin',
title: '',
appRoute: '/app/my-other-plugin',
mount: async (params: AppMountParameters) => {
// mount callback should not use setup dependencies, get start dependencies instead
// so the pluginStart map passed to your renderApp method will be the start deps,
// not the setup deps -- the same asset client is provided to both setup and start in public
const [coreStart, , pluginStart] = await core.getStartServices();
// assetClient is found on pluginStart.assetManager.assetClient
return renderApp(coreStart, pluginStart, params);
},
});
}
}
```
All methods in the public client only require their public options (seen below), and don't require
the "AssetClientDependencies" that are required for the server client versions of the same methods.
This is because the public client will use the asset manager's internal REST API under the hood, where
it will be able to pull the properly-scoped client dependencies off of that request context for you.
### Client methods
#### getHosts
Get a group of host assets found within a specified time range.
| Parameter | Type | Required? | Description |
| :-------- | :-------------- | :-------- | :--------------------------------------------------------------------- |
| from | datetime string | yes | ISO date string representing the START of the time range being queried |
| to | datetime string | yes | ISO date string representing the END of the time range being queried |
**Response**
```json
{
"hosts": [
...found host assets
]
}
```
TODO: Link to a centralized asset document example that each response can reference?

View file

@ -0,0 +1,34 @@
# Asset Manager Plugin Development
These docs contain information you might need if you are developing this plugin in Kibana. If you are interested in the APIs this plugin exposes, please see [./api.md](our API docs) instead.
## Running Tests
There are integration tests for the endpoints implemented thus far as well as for
the sample data tests. There is also a small set of tests meant to ensure that the
plugin is not doing anything without the proper config value in place to enable
the plugin fully. For more on enabling the plugin, see [the docs page](./docs/index.md).
The "not enabled" tests are run by default in CI. To run them manually, do the following:
```shell
$ node scripts/functional_tests_server --config x-pack/test/api_integration/apis/asset_manager/config_when_disabled.ts
$ node scripts/functional_test_runner --config=x-pack/test/api_integration/apis/asset_manager/config_when_disabled.ts
```
The "enabled" tests are NOT run by CI yet, to prevent blocking Kibana development for a
test failure in this alpha, tech preview plugin. They will be moved into the right place
to make them run for CI before the plugin is enabled by default. To run them manually:
```shell
$ node scripts/functional_tests_server --config x-pack/test/api_integration/apis/asset_manager/config.ts
$ node scripts/functional_test_runner --config=x-pack/test/api_integration/apis/asset_manager/config.ts
```
## Using Sample Data
This plugin comes with a full "working set" of sample asset documents, meant
to provide enough data in the correct schema format so that all of the API
endpoints return expected values.
To create the sample data, follow [the instructions in the REST API docs](./docs/index.md#sample-data).

View file

@ -1,24 +1,6 @@
# Asset Manager Documentation
## Deprecated REST API docs
_Note:_ To read about development guidance around testing, sample data, etc., see the
[plugin's main README file](../README.md)
## Alpha Configuration
This plugin is NOT fully enabled by default, even though it's always enabled
by Kibana's definition of "enabled". However, without the following configuration,
it will bail before it sets up any routes or returns anything from its
start, setup, or stop hooks.
To fully enable the plugin, set the following config value in your kibana.yml file:
```yaml
xpack.assetManager.alphaEnabled: true
```
## APIs
This plugin provides the following APIs.
These docs are not being currently maintained because they pertain to an internal REST API. Please see [our docs for our API clients](./api.md) instead.
### Shared Types
@ -58,16 +40,16 @@ Returns a list of assets present within a given time range. Can be limited by as
##### Request
| Option | Type | Required? | Default | Description |
| :------ | :------------ | :-------- | :------ | :--------------------------------------------------------------------------------- |
| from | RangeDate | No | "now-24h" | Starting point for date range to search for assets within |
| to | RangeDate | No | "now" | End point for date range to search for assets |
| type | AssetType[] | No | all | Specify one or more types to restrict the query |
| ean | AssetEan[] | No | all | Specify one or more EANs (specific assets) to restrict the query |
| size | number | No | all | Limit the amount of assets returned |
| Option | Type | Required? | Default | Description |
| :----- | :---------- | :-------- | :-------- | :--------------------------------------------------------------- |
| from | RangeDate | No | "now-24h" | Starting point for date range to search for assets within |
| to | RangeDate | No | "now" | End point for date range to search for assets |
| type | AssetType[] | No | all | Specify one or more types to restrict the query |
| ean | AssetEan[] | No | all | Specify one or more EANs (specific assets) to restrict the query |
| size | number | No | all | Limit the amount of assets returned |
_Notes:_
- User cannot specify both type and ean at the same time.
- For array types such as `type` and `ean`, user should specify the query parameter multiple times, e.g. `type=k8s.pod&type=k8s.node`
@ -410,15 +392,15 @@ GET kbn:/api/asset-manager/assets?from=2023-03-25T17:44:44.000Z&to=2023-03-25T18
Returns assets found in the two time ranges, split by what occurs in only either or in both.
#### Request
#### Request
| Option | Type | Required? | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| aFrom | RangeDate | Yes | N/A | Starting point for baseline date range to search for assets within |
| aTo | RangeDate | Yes | N/A | End point for baseline date range to search for assets within |
| bFrom | RangeDate | Yes | N/A | Starting point for comparison date range |
| bTo | RangeDate | Yes | N/A | End point for comparison date range |
| type | AssetType[] | No | all | Restrict results to one or more asset.type value |
| Option | Type | Required? | Default | Description |
| :----- | :---------- | :-------- | :------ | :----------------------------------------------------------------- |
| aFrom | RangeDate | Yes | N/A | Starting point for baseline date range to search for assets within |
| aTo | RangeDate | Yes | N/A | End point for baseline date range to search for assets within |
| bFrom | RangeDate | Yes | N/A | Starting point for comparison date range |
| bTo | RangeDate | Yes | N/A | End point for comparison date range |
| type | AssetType[] | No | all | Restrict results to one or more asset.type value |
#### Responses
@ -1044,14 +1026,14 @@ Returns assets related to the provided ean. The relation can be one of ancestors
#### Request
| Option | Type | Required? | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| relation | string | Yes | N/A | The type of related assets we're looking for. One of (ancestors|descendants|references) |
| from | RangeDate | Yes | N/A | Starting point for date range to search for assets within |
| to | RangeDate | No | "now" | End point for date range to search for assets |
| ean | AssetEan | Yes | N/A | Single Elastic Asset Name representing the asset for which the related assets are being requested |
| type | AssetType[] | No | all | Restrict results to one or more asset.type value |
| maxDistance | number (1-5) | No | 1 | Maximum number of "hops" to search away from specified asset |
| Option | Type | Required? | Default | Description |
| :---------- | :----------- | :-------- | :------ | :------------------------------------------------------------------------------------------------ | ----------- | ----------- |
| relation | string | Yes | N/A | The type of related assets we're looking for. One of (ancestors | descendants | references) |
| from | RangeDate | Yes | N/A | Starting point for date range to search for assets within |
| to | RangeDate | No | "now" | End point for date range to search for assets |
| ean | AssetEan | Yes | N/A | Single Elastic Asset Name representing the asset for which the related assets are being requested |
| type | AssetType[] | No | all | Restrict results to one or more asset.type value |
| maxDistance | number (1-5) | No | 1 | Maximum number of "hops" to search away from specified asset |
#### Responses

View file

@ -15,7 +15,7 @@
"apmDataAccess",
"metricsDataAccess"
],
"browser": false,
"browser": true,
"server": true,
"requiredBundles": [
]

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
import { Plugin } from './plugin';
import { AssetManagerPublicPluginSetup, AssetManagerPublicPluginStart } from './types';
export const plugin: PluginInitializer<
AssetManagerPublicPluginSetup | undefined,
AssetManagerPublicPluginStart | undefined
> = (context: PluginInitializerContext) => {
return new Plugin(context);
};
export type { AssetManagerPublicPluginSetup, AssetManagerPublicPluginStart };
export type AssetManagerAppId = 'assetManager';

View file

@ -0,0 +1,48 @@
/*
* 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 return the direct results of http.get', async () => {
const client = new PublicAssetsClient(http);
http.get.mockResolvedValueOnce('my result');
const result = await client.getHosts({ from: 'x', to: 'y' });
expect(result).toBe('my result');
});
});
});

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { HttpStart } from '@kbn/core/public';
import { GetHostsOptionsPublic } from '../../common/types_client';
import { GetHostAssetsResponse } from '../../common/types_api';
import { GET_HOSTS } from '../../common/constants_routes';
import { IPublicAssetsClient } from '../types';
export class PublicAssetsClient implements IPublicAssetsClient {
constructor(private readonly http: HttpStart) {}
async getHosts(options: GetHostsOptionsPublic) {
const results = await this.http.get<GetHostAssetsResponse>(GET_HOSTS, {
query: {
...options,
},
});
return results;
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
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';
export class Plugin implements AssetManagerPluginClass {
public config: AssetManagerPublicConfig;
public logger: Logger;
constructor(context: PluginInitializerContext<{}>) {
this.config = context.config.get();
this.logger = context.logger.get();
}
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);
return {
publicAssetsClient,
};
}
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);
return {
publicAssetsClient,
};
}
stop() {}
}

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 type { Plugin as PluginClass } from '@kbn/core/public';
import { GetHostsOptionsPublic } from '../common/types_client';
import { GetHostAssetsResponse } from '../common/types_api';
export interface AssetManagerPublicPluginSetup {
publicAssetsClient: IPublicAssetsClient;
}
export interface AssetManagerPublicPluginStart {
publicAssetsClient: IPublicAssetsClient;
}
export type AssetManagerPluginClass = PluginClass<
AssetManagerPublicPluginSetup | undefined,
AssetManagerPublicPluginStart | undefined
>;
export interface IPublicAssetsClient {
getHosts: (options: GetHostsOptionsPublic) => Promise<GetHostAssetsResponse>;
}

View file

@ -6,4 +6,3 @@
*/
export const ASSETS_INDEX_PREFIX = 'assets';
export const ASSET_MANAGER_API_BASE = '/api/asset-manager';

View file

@ -6,11 +6,21 @@
*/
import { PluginInitializerContext } from '@kbn/core-plugins-server';
import { AssetManagerServerPlugin, config } from './plugin';
import { AssetManagerConfig } from '../common/config';
import {
AssetManagerServerPlugin,
AssetManagerServerPluginSetup,
AssetManagerServerPluginStart,
config,
} from './plugin';
import type { WriteSamplesPostBody } from './routes/sample_assets';
import { AssetManagerConfig } from './types';
export type { AssetManagerConfig, WriteSamplesPostBody };
export type {
AssetManagerConfig,
WriteSamplesPostBody,
AssetManagerServerPluginSetup,
AssetManagerServerPluginStart,
};
export { config };
export const plugin = (context: PluginInitializerContext<AssetManagerConfig>) =>

View file

@ -13,7 +13,7 @@ export async function getHostsByAssets(
options: GetHostsOptionsInjected
): Promise<{ hosts: Asset[] }> {
const hosts = await getAssets({
esClient: options.esClient,
elasticsearchClient: options.elasticsearchClient,
filters: {
kind: 'host',
from: options.from,

View file

@ -13,11 +13,11 @@ export async function getHostsBySignals(
options: GetHostsOptionsInjected
): Promise<{ hosts: Asset[] }> {
const metricsIndices = await options.metricsClient.getMetricIndices({
savedObjectsClient: options.soClient,
savedObjectsClient: options.savedObjectsClient,
});
const { assets } = await collectHosts({
client: options.esClient,
client: options.elasticsearchClient,
from: options.from,
to: options.to,
sourceIndices: {

View file

@ -5,12 +5,11 @@
* 2.0.
*/
import { AccessorOptions, OptionsWithInjectedValues } from '..';
import type { AssetClientDependencies } from '../../../types';
import type { GetHostsOptionsPublic } from '../../../../common/types_client';
import type { OptionsWithInjectedValues } from '..';
export interface GetHostsOptions extends AccessorOptions {
from: string;
to: string;
}
export type GetHostsOptions = GetHostsOptionsPublic & AssetClientDependencies;
export type GetHostsOptionsInjected = OptionsWithInjectedValues<GetHostsOptions>;
export interface HostIdentifier {

View file

@ -5,11 +5,10 @@
* 2.0.
*/
import { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import { APMDataAccessConfig } from '@kbn/apm-data-access-plugin/server';
import { MetricsDataClient } from '@kbn/metrics-data-access-plugin/server';
import { SavedObjectsClientContract } from '@kbn/core/server';
import { AssetManagerConfig } from '../../types';
import { AssetManagerConfig } from '../../../common/config';
export interface InjectedValues {
sourceIndices: AssetManagerConfig['sourceIndices'];
@ -18,8 +17,3 @@ export interface InjectedValues {
}
export type OptionsWithInjectedValues<T extends object> = T & InjectedValues;
export interface AccessorOptions {
esClient: ElasticsearchClient;
soClient: SavedObjectsClientContract;
}

View file

@ -18,7 +18,7 @@ export async function getServicesByAssets(
}
const services = await getAssets({
esClient: options.esClient,
elasticsearchClient: options.elasticsearchClient,
filters: {
kind: 'service',
from: options.from,
@ -32,7 +32,7 @@ export async function getServicesByAssets(
async function getServicesByParent(
options: GetServicesOptionsInjected
): Promise<{ services: Asset[] }> {
const { descendants } = await getAllRelatedAssets(options.esClient, {
const { descendants } = await getAllRelatedAssets(options.elasticsearchClient, {
from: options.from,
to: options.to,
maxDistance: 5,

View file

@ -26,9 +26,9 @@ export async function getServicesBySignals(
});
}
const apmIndices = await options.getApmIndices(options.soClient);
const apmIndices = await options.getApmIndices(options.savedObjectsClient);
const { assets } = await collectServices({
client: options.esClient,
client: options.elasticsearchClient,
from: options.from,
to: options.to,
sourceIndices: {

View file

@ -5,13 +5,11 @@
* 2.0.
*/
import { AccessorOptions, OptionsWithInjectedValues } from '..';
import { AssetClientDependencies } from '../../../types';
import { GetServicesOptionsPublic } from '../../../../common/types_client';
import { OptionsWithInjectedValues } from '..';
export interface GetServicesOptions extends AccessorOptions {
from: string;
to: string;
parent?: string;
}
export type GetServicesOptions = GetServicesOptionsPublic & AssetClientDependencies;
export type GetServicesOptionsInjected = OptionsWithInjectedValues<GetServicesOptions>;
export interface ServiceIdentifier {

View file

@ -8,8 +8,8 @@
import { APMDataAccessConfig } from '@kbn/apm-data-access-plugin/server';
import { MetricsDataClient } from '@kbn/metrics-data-access-plugin/server';
import { SavedObjectsClientContract } from '@kbn/core/server';
import { AssetManagerConfig } from '../../common/config';
import { Asset } from '../../common/types_api';
import { AssetManagerConfig } from '../types';
import { OptionsWithInjectedValues } from './accessors';
import { GetHostsOptions } from './accessors/hosts';
import { GetServicesOptions } from './accessors/services';
@ -18,28 +18,28 @@ import { getHostsBySignals } from './accessors/hosts/get_hosts_by_signals';
import { getServicesByAssets } from './accessors/services/get_services_by_assets';
import { getServicesBySignals } from './accessors/services/get_services_by_signals';
interface AssetAccessorClassOptions {
interface AssetClientClassOptions {
sourceIndices: AssetManagerConfig['sourceIndices'];
source: AssetManagerConfig['lockedSource'];
getApmIndices: (soClient: SavedObjectsClientContract) => Promise<APMDataAccessConfig['indices']>;
metricsClient: MetricsDataClient;
}
export class AssetAccessor {
constructor(private options: AssetAccessorClassOptions) {}
export class AssetClient {
constructor(private baseOptions: AssetClientClassOptions) {}
injectOptions<T extends object = {}>(options: T): OptionsWithInjectedValues<T> {
return {
...options,
sourceIndices: this.options.sourceIndices,
getApmIndices: this.options.getApmIndices,
metricsClient: this.options.metricsClient,
sourceIndices: this.baseOptions.sourceIndices,
getApmIndices: this.baseOptions.getApmIndices,
metricsClient: this.baseOptions.metricsClient,
};
}
async getHosts(options: GetHostsOptions): Promise<{ hosts: Asset[] }> {
const withInjected = this.injectOptions(options);
if (this.options.source === 'assets') {
if (this.baseOptions.source === 'assets') {
return await getHostsByAssets(withInjected);
} else {
return await getHostsBySignals(withInjected);
@ -48,7 +48,7 @@ export class AssetAccessor {
async getServices(options: GetServicesOptions): Promise<{ services: Asset[] }> {
const withInjected = this.injectOptions(options);
if (this.options.source === 'assets') {
if (this.baseOptions.source === 'assets') {
return await getServicesByAssets(withInjected);
} else {
return await getServicesBySignals(withInjected);

View file

@ -26,13 +26,13 @@ interface GetAllRelatedAssetsOptions {
}
export async function getAllRelatedAssets(
esClient: ElasticsearchClient,
elasticsearchClient: ElasticsearchClient,
options: GetAllRelatedAssetsOptions
) {
// How to put size into this?
const { ean, from, to, relation, maxDistance, kind = [] } = options;
const primary = await findPrimary(esClient, { ean, from, to });
const primary = await findPrimary(elasticsearchClient, { ean, from, to });
let assetsToFetch = [primary];
let currentDistance = 1;
@ -52,7 +52,7 @@ export async function getAllRelatedAssets(
const results = flatten(
await Promise.all(
assetsToFetch.map((asset) => findRelatedAssets(esClient, asset, queryOptions))
assetsToFetch.map((asset) => findRelatedAssets(elasticsearchClient, asset, queryOptions))
)
);
@ -75,11 +75,11 @@ export async function getAllRelatedAssets(
}
async function findPrimary(
esClient: ElasticsearchClient,
elasticsearchClient: ElasticsearchClient,
{ ean, from, to }: Pick<GetAllRelatedAssetsOptions, 'ean' | 'from' | 'to'>
): Promise<Asset> {
const primaryResults = await getAssets({
esClient,
elasticsearchClient,
size: 1,
filters: { ean, from, to },
});
@ -101,7 +101,7 @@ type FindRelatedAssetsOptions = Pick<
> & { visitedEans: string[] };
async function findRelatedAssets(
esClient: ElasticsearchClient,
elasticsearchClient: ElasticsearchClient,
primary: Asset,
{ relation, from, to, kind, visitedEans }: FindRelatedAssetsOptions
): Promise<Asset[]> {
@ -116,7 +116,7 @@ async function findRelatedAssets(
const remainingEansToFind = without(directlyRelatedEans, ...visitedEans);
if (remainingEansToFind.length > 0) {
directlyRelatedAssets = await getAssets({
esClient,
elasticsearchClient,
filters: { ean: remainingEansToFind, from, to, kind },
});
}
@ -124,7 +124,7 @@ async function findRelatedAssets(
debug('Directly related assets found:', JSON.stringify(directlyRelatedAssets));
const indirectlyRelatedAssets = await getIndirectlyRelatedAssets({
esClient,
elasticsearchClient,
ean: primary['asset.ean'],
excludeEans: visitedEans.concat(directlyRelatedEans),
relation,

View file

@ -19,7 +19,7 @@ interface GetAssetsOptions extends ElasticsearchAccessorOptions {
}
export async function getAssets({
esClient,
elasticsearchClient,
size = 100,
filters = {},
}: GetAssetsOptions): Promise<Asset[]> {
@ -125,6 +125,6 @@ export async function getAssets({
debug('Performing Get Assets Query', '\n\n', JSON.stringify(dsl, null, 2));
const response = await esClient.search<Asset>(dsl);
const response = await elasticsearchClient.search<Asset>(dsl);
return response.hits.hits.map((hit) => hit._source).filter((asset): asset is Asset => !!asset);
}

View file

@ -23,7 +23,7 @@ interface GetRelatedAssetsOptions extends ElasticsearchAccessorOptions {
}
export async function getIndirectlyRelatedAssets({
esClient,
elasticsearchClient,
size = 100,
from = 'now-24h',
to = 'now',
@ -91,7 +91,7 @@ export async function getIndirectlyRelatedAssets({
debug('Performing Indirectly Related Asset Query', '\n\n', JSON.stringify(dsl, null, 2));
const response = await esClient.search<Asset>(dsl);
const response = await elasticsearchClient.search<Asset>(dsl);
return response.hits.hits.map((hit) => hit._source).filter((asset): asset is Asset => !!asset);
}

View file

@ -18,7 +18,7 @@ interface WriteAssetsOptions extends ElasticsearchAccessorOptions {
}
export async function writeAssets({
esClient,
elasticsearchClient,
assetDocs,
namespace = 'default',
refresh = false,
@ -33,5 +33,5 @@ export async function writeAssets({
debug('Performing Write Asset Query', '\n\n', JSON.stringify(dsl, null, 2));
return await esClient.bulk<{}>(dsl);
return await elasticsearchClient.bulk<{}>(dsl);
}

View file

@ -18,15 +18,16 @@ import {
import { upsertTemplate } from './lib/manage_index_templates';
import { setupRoutes } from './routes';
import { assetsIndexTemplateConfig } from './templates/assets_template';
import { AssetManagerConfig, configSchema } from './types';
import { AssetAccessor } from './lib/asset_accessor';
import { AssetClient } from './lib/asset_client';
import { AssetManagerPluginSetupDependencies, AssetManagerPluginStartDependencies } from './types';
import { AssetManagerConfig, configSchema, exposeToBrowserConfig } from '../common/config';
export type AssetManagerServerPluginSetup = ReturnType<AssetManagerServerPlugin['setup']>;
export type AssetManagerServerPluginStart = ReturnType<AssetManagerServerPlugin['start']>;
export const config: PluginConfigDescriptor<AssetManagerConfig> = {
schema: configSchema,
exposeToBrowser: exposeToBrowserConfig,
};
export class AssetManagerServerPlugin
@ -49,13 +50,13 @@ export class AssetManagerServerPlugin
public setup(core: CoreSetup, plugins: AssetManagerPluginSetupDependencies) {
// Check for config value and bail out if not "alpha-enabled"
if (!this.config.alphaEnabled) {
this.logger.info('Asset manager plugin [tech preview] is NOT enabled');
this.logger.info('Server is NOT enabled');
return;
}
this.logger.info('Asset manager plugin [tech preview] is enabled');
this.logger.info('Server is enabled');
const assetAccessor = new AssetAccessor({
const assetClient = new AssetClient({
source: this.config.lockedSource,
sourceIndices: this.config.sourceIndices,
getApmIndices: plugins.apmDataAccess.getApmIndices,
@ -63,10 +64,10 @@ export class AssetManagerServerPlugin
});
const router = core.http.createRouter();
setupRoutes<RequestHandlerContext>({ router, assetAccessor });
setupRoutes<RequestHandlerContext>({ router, assetClient });
return {
assetAccessor,
assetClient,
};
}

View file

@ -5,56 +5,37 @@
* 2.0.
*/
import * as rt from 'io-ts';
import datemath from '@kbn/datemath';
import {
dateRt,
inRangeFromStringRt,
datemathStringRt,
createRouteValidationFunction,
createLiteralValueFromUndefinedRT,
} from '@kbn/io-ts-utils';
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 { ASSET_MANAGER_API_BASE } from '../../constants';
import { getEsClientFromContext } from '../utils';
const sizeRT = rt.union([inRangeFromStringRt(1, 100), createLiteralValueFromUndefinedRT(10)]);
const assetDateRT = rt.union([dateRt, datemathStringRt]);
const getHostAssetsQueryOptionsRT = rt.exact(
rt.partial({
from: assetDateRT,
to: assetDateRT,
size: sizeRT,
})
);
export type GetHostAssetsQueryOptions = rt.TypeOf<typeof getHostAssetsQueryOptionsRT>;
import * as routePaths from '../../../common/constants_routes';
import { getClientsFromContext } from '../utils';
export function hostsRoutes<T extends RequestHandlerContext>({
router,
assetAccessor,
assetClient,
}: SetupRouteOptions<T>) {
router.get<unknown, GetHostAssetsQueryOptions, unknown>(
{
path: `${ASSET_MANAGER_API_BASE}/assets/hosts`,
path: routePaths.GET_HOSTS,
validate: {
query: createRouteValidationFunction(getHostAssetsQueryOptionsRT),
},
},
async (context, req, res) => {
const { from = 'now-24h', to = 'now' } = req.query || {};
const esClient = await getEsClientFromContext(context);
const coreContext = await context.core;
const soClient = coreContext.savedObjects.client;
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
try {
const response = await assetAccessor.getHosts({
const response = await assetClient.getHosts({
from: datemath.parse(from)!.toISOString(),
to: datemath.parse(to)!.toISOString(),
esClient,
soClient,
elasticsearchClient,
savedObjectsClient,
});
return res.ok({ body: response });

View file

@ -17,11 +17,11 @@ import {
} from '@kbn/io-ts-utils';
import { debug } from '../../../common/debug_log';
import { assetTypeRT, assetKindRT, relationRT } from '../../../common/types_api';
import { ASSET_MANAGER_API_BASE } from '../../constants';
import { GET_ASSETS, GET_RELATED_ASSETS, GET_ASSETS_DIFF } from '../../../common/constants_routes';
import { getAssets } from '../../lib/get_assets';
import { getAllRelatedAssets } from '../../lib/get_all_related_assets';
import { SetupRouteOptions } from '../types';
import { getEsClientFromContext } from '../utils';
import { getClientsFromContext } from '../utils';
import { AssetNotFoundError } from '../../lib/errors';
import { isValidRange } from '../../lib/utils';
@ -82,7 +82,7 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
// GET /assets
router.get<unknown, GetAssetsQueryOptions, unknown>(
{
path: `${ASSET_MANAGER_API_BASE}/assets`,
path: GET_ASSETS,
validate: {
query: createRouteValidationFunction(getAssetsQueryOptionsRT),
},
@ -102,10 +102,10 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
});
}
const esClient = await getEsClientFromContext(context);
const { elasticsearchClient } = await getClientsFromContext(context);
try {
const results = await getAssets({ esClient, size, filters });
const results = await getAssets({ elasticsearchClient, size, filters });
return res.ok({ body: { results } });
} catch (error: unknown) {
debug('error looking up asset records', error);
@ -120,7 +120,7 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
// GET assets/related
router.get<unknown, GetRelatedAssetsQueryOptions, unknown>(
{
path: `${ASSET_MANAGER_API_BASE}/assets/related`,
path: GET_RELATED_ASSETS,
validate: {
query: createRouteValidationFunction(getRelatedAssetsQueryOptionsRT),
},
@ -129,7 +129,7 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
// Add references into sample data and write integration tests
const { from, to, ean, relation, maxDistance, size, type, kind } = req.query || {};
const esClient = await getEsClientFromContext(context);
const { elasticsearchClient } = await getClientsFromContext(context);
if (to && !isValidRange(from, to)) {
return res.badRequest({
@ -140,7 +140,7 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
try {
return res.ok({
body: {
results: await getAllRelatedAssets(esClient, {
results: await getAllRelatedAssets(elasticsearchClient, {
ean,
from,
to,
@ -165,7 +165,7 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
// GET /assets/diff
router.get<unknown, GetAssetsDiffQueryOptions, unknown>(
{
path: `${ASSET_MANAGER_API_BASE}/assets/diff`,
path: GET_ASSETS_DIFF,
validate: {
query: createRouteValidationFunction(getAssetsDiffQueryOptionsRT),
},
@ -187,11 +187,11 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
});
}
const esClient = await getEsClientFromContext(context);
const { elasticsearchClient } = await getClientsFromContext(context);
try {
const resultsForA = await getAssets({
esClient,
elasticsearchClient,
filters: {
from: aFrom,
to: aTo,
@ -201,7 +201,7 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
});
const resultsForB = await getAssets({
esClient,
elasticsearchClient,
filters: {
from: bFrom,
to: bTo,

View file

@ -17,8 +17,8 @@ import {
import { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
import { debug } from '../../../common/debug_log';
import { SetupRouteOptions } from '../types';
import { ASSET_MANAGER_API_BASE } from '../../constants';
import { getEsClientFromContext } from '../utils';
import { ASSET_MANAGER_API_BASE } from '../../../common/constants_routes';
import { getClientsFromContext } from '../utils';
const sizeRT = rt.union([inRangeFromStringRt(1, 100), createLiteralValueFromUndefinedRT(10)]);
const assetDateRT = rt.union([dateRt, datemathStringRt]);
@ -35,7 +35,7 @@ export type GetServiceAssetsQueryOptions = rt.TypeOf<typeof getServiceAssetsQuer
export function servicesRoutes<T extends RequestHandlerContext>({
router,
assetAccessor,
assetClient,
}: SetupRouteOptions<T>) {
// GET /assets/services
router.get<unknown, GetServiceAssetsQueryOptions, unknown>(
@ -47,16 +47,14 @@ export function servicesRoutes<T extends RequestHandlerContext>({
},
async (context, req, res) => {
const { from = 'now-24h', to = 'now', parent } = req.query || {};
const esClient = await getEsClientFromContext(context);
const coreContext = await context.core;
const soClient = coreContext.savedObjects.client;
const { elasticsearchClient, savedObjectsClient } = await getClientsFromContext(context);
try {
const response = await assetAccessor.getServices({
const response = await assetClient.getServices({
from: datemath.parse(from)!.toISOString(),
to: datemath.parse(to)!.toISOString(),
parent,
esClient,
soClient,
elasticsearchClient,
savedObjectsClient,
});
return res.ok({ body: response });

View file

@ -15,11 +15,11 @@ import { servicesRoutes } from './assets/services';
export function setupRoutes<T extends RequestHandlerContext>({
router,
assetAccessor,
assetClient,
}: SetupRouteOptions<T>) {
pingRoute<T>({ router, assetAccessor });
assetsRoutes<T>({ router, assetAccessor });
sampleAssetsRoutes<T>({ router, assetAccessor });
hostsRoutes<T>({ router, assetAccessor });
servicesRoutes<T>({ router, assetAccessor });
pingRoute<T>({ router, assetClient });
assetsRoutes<T>({ router, assetClient });
sampleAssetsRoutes<T>({ router, assetClient });
hostsRoutes<T>({ router, assetClient });
servicesRoutes<T>({ router, assetClient });
}

View file

@ -6,7 +6,7 @@
*/
import { RequestHandlerContextBase } from '@kbn/core-http-server';
import { ASSET_MANAGER_API_BASE } from '../constants';
import { ASSET_MANAGER_API_BASE } from '../../common/constants_routes';
import { SetupRouteOptions } from './types';
export function pingRoute<T extends RequestHandlerContextBase>({ router }: SetupRouteOptions<T>) {

View file

@ -7,11 +7,11 @@
import { schema } from '@kbn/config-schema';
import { RequestHandlerContext } from '@kbn/core/server';
import { ASSET_MANAGER_API_BASE } from '../constants';
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 { getEsClientFromContext } from './utils';
import { getClientsFromContext } from './utils';
export type WriteSamplesPostBody = {
baseDateTime?: string | number;
@ -62,12 +62,12 @@ export function sampleAssetsRoutes<T extends RequestHandlerContext>({
},
});
}
const esClient = await getEsClientFromContext(context);
const { elasticsearchClient } = await getClientsFromContext(context);
const assetDocs = getSampleAssetDocs({ baseDateTime: parsed, excludeEans });
try {
const response = await writeAssets({
esClient,
elasticsearchClient,
assetDocs,
namespace: 'sample_data',
refresh,
@ -101,9 +101,9 @@ export function sampleAssetsRoutes<T extends RequestHandlerContext>({
validate: {},
},
async (context, req, res) => {
const esClient = await getEsClientFromContext(context);
const { elasticsearchClient } = await getClientsFromContext(context);
const sampleDataStreams = await esClient.indices.getDataStream({
const sampleDataStreams = await elasticsearchClient.indices.getDataStream({
name: 'assets-*-sample_data',
expand_wildcards: 'all',
});
@ -115,7 +115,7 @@ export function sampleAssetsRoutes<T extends RequestHandlerContext>({
for (let i = 0; i < dataStreamsToDelete.length; i++) {
const dsName = dataStreamsToDelete[i];
try {
await esClient.indices.deleteDataStream({ name: dsName });
await elasticsearchClient.indices.deleteDataStream({ name: dsName });
deletedDataStreams.push(dsName);
} catch (error: any) {
errorWhileDeleting =

View file

@ -6,9 +6,9 @@
*/
import { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server';
import { AssetAccessor } from '../lib/asset_accessor';
import { AssetClient } from '../lib/asset_client';
export interface SetupRouteOptions<T extends RequestHandlerContextBase> {
router: IRouter<T>;
assetAccessor: AssetAccessor;
assetClient: AssetClient;
}

View file

@ -7,6 +7,12 @@
import { RequestHandlerContext } from '@kbn/core/server';
export async function getEsClientFromContext<T extends RequestHandlerContext>(context: T) {
return (await context.core).elasticsearch.client.asCurrentUser;
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,
};
}

View file

@ -5,8 +5,7 @@
* 2.0.
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { ElasticsearchClient } from '@kbn/core/server';
import { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import {
ApmDataAccessPluginSetup,
ApmDataAccessPluginStart,
@ -14,37 +13,9 @@ import {
import { MetricsDataPluginSetup } from '@kbn/metrics-data-access-plugin/server';
export interface ElasticsearchAccessorOptions {
esClient: ElasticsearchClient;
elasticsearchClient: ElasticsearchClient;
}
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 }
),
// Choose an explicit source for asset queries.
// NOTE: This will eventually need to be able to cleverly switch
// between these values based on the availability of data in the
// indices, and possibly for each asset kind/type value.
// For now, we set this explicitly.
lockedSource: schema.oneOf([schema.literal('assets'), schema.literal('signals')], {
defaultValue: 'signals',
}),
});
export type AssetManagerConfig = TypeOf<typeof configSchema>;
export interface AssetManagerPluginSetupDependencies {
apmDataAccess: ApmDataAccessPluginSetup;
metricsDataAccess: MetricsDataPluginSetup;
@ -52,3 +23,8 @@ export interface AssetManagerPluginSetupDependencies {
export interface AssetManagerPluginStartDependencies {
apmDataAccess: ApmDataAccessPluginStart;
}
export interface AssetClientDependencies {
elasticsearchClient: ElasticsearchClient;
savedObjectsClient: SavedObjectsClientContract;
}

View file

@ -7,6 +7,7 @@
"../../../typings/**/*",
"common/**/*",
"server/**/*",
"public/**/*",
"types/**/*"
],
"exclude": ["target/**/*"],
@ -17,10 +18,11 @@
"@kbn/core-http-server",
"@kbn/core-elasticsearch-client-server-mocks",
"@kbn/io-ts-utils",
"@kbn/core-elasticsearch-server",
"@kbn/core-http-request-handler-context-server",
"@kbn/datemath",
"@kbn/apm-data-access-plugin",
"@kbn/core-http-browser-mocks",
"@kbn/logging",
"@kbn/metrics-data-access-plugin"
]
}