[asset_manager] Add tests for ean filtering (#153725)

## Summary

- Added some tests that verify the functionality already added by
@jasonrhodes
- Made some small changes to the types and used kbn.schema in route
validation
- Swapped from `term` to `terms` filter for the `ean` filter
- Added a check that throws a 400 if both `type` and `ean` are used at
the same time
- Updated the docs to show the new request responses
- Mark `from` option as optional since it has a default value

Closes #153461
This commit is contained in:
Milton Hultgren 2023-04-03 20:54:11 +02:00 committed by GitHub
parent fa1812b293
commit 210a7eb335
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 340 additions and 111 deletions

View file

@ -112,7 +112,7 @@ export interface K8sCluster extends WithTimestamp {
export interface AssetFilters {
type?: AssetType | AssetType[];
kind?: AssetKind;
ean?: string;
ean?: string | string[];
id?: string;
typeLike?: string;
eanLike?: string;

View file

@ -54,102 +54,213 @@ query parameter key multiple times, e.g. `?type=k8s.pod&type=k8s.node`**
#### GET /assets
Returns a list of assets present within a given time range. Can be limited by asset type OR EAN.
Returns a list of assets present within a given time range. Can be limited by asset type OR EAN (Elastic Asset Name).
##### Request
| Option | Type | Required? | Default | Description |
| :------ | :------------ | :-------- | :------ | :--------------------------------------------------------------------------------- |
| from | RangeDate | Yes | N/A | Starting point for date range to search for assets within |
| 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 |
| query\* | ESQueryString | No | n/a | Include a Lucene query string to be applied, for more general ECS-based filtering. |
| 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 |
\*query options not implemented yet
_Note: Cannot specify both `type` and `ean` at the same time. Also, specifying the `query` param can lead to surprising results if it overlaps with the other parameters._
_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`
##### Responses
<details>
<summary>Invalid request with type and ean both specified</summary>
```curl
GET /assets?from=2023-03-25T17:44:44.000Z&type=k8s.pod&ean=k8s.pod:123
{
"statusCode": 400,
"error": "Bad Request",
"message": "Filters "type" and "ean" are mutually exclusive but found both."
}
```
</details>
<details>
<summary>All assets JSON response</summary>
```curl
GET /assets?from=2022-02-07T00:00:00.000Z&to=2022-02-07T16:00:00.000Z
GET /assets?from=2023-03-25T17:44:44.000Z&to=2023-03-25T18:44:44.000Z
{
"results": [
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.cluster",
"asset.id": "my-cluster-1",
"asset.name": "my-cluster-1",
"asset.ean": "k8s.cluster:my-cluster-1",
"asset.parents": [],
"asset.children": [],
"asset.references": []
"asset.id": "cluster-001",
"asset.name": "Cluster 001 (AWS EKS)",
"asset.ean": "k8s.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"
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.cluster",
"asset.id": "cluster-002",
"asset.name": "Cluster 002 (Azure AKS)",
"asset.ean": "k8s.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"
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.node",
"asset.id": "kn101",
"asset.name": "node-101",
"asset.id": "node-101",
"asset.name": "k8s-node-101-aws",
"asset.ean": "k8s.node:node-101",
"asset.parents": ["k8s.cluster:my-cluster-1"],
"asset.children": [],
"asset.references": []
"asset.parents": [
"k8s.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"
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.node",
"asset.id": "kn102",
"asset.name": "node-102",
"asset.id": "node-102",
"asset.name": "k8s-node-102-aws",
"asset.ean": "k8s.node:node-102",
"asset.parents": ["k8s.cluster:my-cluster-1"],
"asset.children": [],
"asset.references": []
"asset.parents": [
"k8s.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"
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"asset.type": "k8s.pod",
"asset.id": "kp2040",
"asset.name": "pod-2q5m",
"asset.ean": "k8s.pod:pod-2q5m",
"asset.parents": ["k8s.node:node-101"],
"asset.children": [],
"asset.references": []
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.node",
"asset.id": "node-103",
"asset.name": "k8s-node-103-aws",
"asset.ean": "k8s.node:node-103",
"asset.parents": [
"k8s.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"
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp2055",
"asset.name": "pod-6r1z",
"asset.ean": "k8s.pod:pod-6r1z",
"asset.parents": ["k8s.node:node-101"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200xrg1",
"asset.name": "k8s-pod-200xrg1-aws",
"asset.ean": "k8s.pod:pod-200xrg1",
"asset.parents": [
"k8s.node:node-101"
]
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp3987",
"asset.name": "pod-9e2w",
"asset.ean": "k8s.pod:pod-9e2w",
"asset.parents": ["k8s.node:node-102"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200dfp2",
"asset.name": "k8s-pod-200dfp2-aws",
"asset.ean": "k8s.pod:pod-200dfp2",
"asset.parents": [
"k8s.node:node-101"
]
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp3987",
"asset.name": "pod-9e2w",
"asset.ean": "k8s.pod:pod-9e2w",
"asset.parents": ["k8s.node:node-102"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200wwc3",
"asset.name": "k8s-pod-200wwc3-aws",
"asset.ean": "k8s.pod:pod-200wwc3",
"asset.parents": [
"k8s.node:node-101"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200naq4",
"asset.name": "k8s-pod-200naq4-aws",
"asset.ean": "k8s.pod:pod-200naq4",
"asset.parents": [
"k8s.node:node-102"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200ohr5",
"asset.name": "k8s-pod-200ohr5-aws",
"asset.ean": "k8s.pod:pod-200ohr5",
"asset.parents": [
"k8s.node:node-102"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200yyx6",
"asset.name": "k8s-pod-200yyx6-aws",
"asset.ean": "k8s.pod:pod-200yyx6",
"asset.parents": [
"k8s.node:node-103"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200psd7",
"asset.name": "k8s-pod-200psd7-aws",
"asset.ean": "k8s.pod:pod-200psd7",
"asset.parents": [
"k8s.node:node-103"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200wmc8",
"asset.name": "k8s-pod-200wmc8-aws",
"asset.ean": "k8s.pod:pod-200wmc8",
"asset.parents": [
"k8s.node:node-103"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200ugg9",
"asset.name": "k8s-pod-200ugg9-aws",
"asset.ean": "k8s.pod:pod-200ugg9",
"asset.parents": [
"k8s.node:node-103"
]
}
]
}
@ -162,49 +273,99 @@ GET /assets?from=2022-02-07T00:00:00.000Z&to=2022-02-07T16:00:00.000Z
<summary>Assets by type JSON response</summary>
```curl
GET /assets?from=2022-02-07T00:00:00.000Z&to=2022-02-07T16:00:00.000Z&types=k8s.pod
GET /assets?from=2023-03-25T17:44:44.000Z&to=2023-03-25T18:44:44.000Z&type=k8s.pod
{
"results": [
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp2040",
"asset.name": "pod-2q5m",
"asset.ean": "k8s.pod:pod-2q5m",
"asset.parents": ["k8s.node:node-101"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200xrg1",
"asset.name": "k8s-pod-200xrg1-aws",
"asset.ean": "k8s.pod:pod-200xrg1",
"asset.parents": [
"k8s.node:node-101"
]
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp2055",
"asset.name": "pod-6r1z",
"asset.ean": "k8s.pod:pod-6r1z",
"asset.parents": ["k8s.node:node-101"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200dfp2",
"asset.name": "k8s-pod-200dfp2-aws",
"asset.ean": "k8s.pod:pod-200dfp2",
"asset.parents": [
"k8s.node:node-101"
]
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp3987",
"asset.name": "pod-9e2w",
"asset.ean": "k8s.pod:pod-9e2w",
"asset.parents": ["k8s.node:node-102"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200wwc3",
"asset.name": "k8s-pod-200wwc3-aws",
"asset.ean": "k8s.pod:pod-200wwc3",
"asset.parents": [
"k8s.node:node-101"
]
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "kp3987",
"asset.name": "pod-9e2w",
"asset.ean": "k8s.pod:pod-9e2w",
"asset.parents": ["k8s.node:node-102"],
"asset.children": [],
"asset.references": []
"asset.id": "pod-200naq4",
"asset.name": "k8s-pod-200naq4-aws",
"asset.ean": "k8s.pod:pod-200naq4",
"asset.parents": [
"k8s.node:node-102"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200ohr5",
"asset.name": "k8s-pod-200ohr5-aws",
"asset.ean": "k8s.pod:pod-200ohr5",
"asset.parents": [
"k8s.node:node-102"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200yyx6",
"asset.name": "k8s-pod-200yyx6-aws",
"asset.ean": "k8s.pod:pod-200yyx6",
"asset.parents": [
"k8s.node:node-103"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200psd7",
"asset.name": "k8s-pod-200psd7-aws",
"asset.ean": "k8s.pod:pod-200psd7",
"asset.parents": [
"k8s.node:node-103"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200wmc8",
"asset.name": "k8s-pod-200wmc8-aws",
"asset.ean": "k8s.pod:pod-200wmc8",
"asset.parents": [
"k8s.node:node-103"
]
},
{
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200ugg9",
"asset.name": "k8s-pod-200ugg9-aws",
"asset.ean": "k8s.pod:pod-200ugg9",
"asset.parents": [
"k8s.node:node-103"
]
}
]
}
@ -217,29 +378,25 @@ GET /assets?from=2022-02-07T00:00:00.000Z&to=2022-02-07T16:00:00.000Z&types=k8s.
<summary>Assets by EAN JSON response</summary>
```curl
GET /assets?from=2022-02-07T00:00:00.000Z&to=2022-02-07T16:00:00.000Z&eans=k8s.node:node-101,k8s.pod:pod-6r1z
GET /assets?from=2023-03-25T17:44:44.000Z&to=2023-03-25T18:44:44.000Z&ean=k8s.node:node-101&ean=k8s.pod:pod-6r1z
{
"results": [
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"@timestamp": "2023-03-25T18:35:38.369Z",
"asset.type": "k8s.node",
"asset.id": "kn101",
"asset.name": "node-101",
"asset.id": "node-101",
"asset.name": "k8s-node-101-aws",
"asset.ean": "k8s.node:node-101",
"asset.parents": ["k8s.cluster:my-cluster-1"],
"asset.children": [],
"asset.references": []
},
{
"@timestamp": "2022-02-07T14:04:40.000Z",
"asset.type": "k8s.pod",
"asset.id": "kp2055",
"asset.name": "pod-6r1z",
"asset.ean": "k8s.pod:pod-6r1z",
"asset.parents": ["k8s.node:node-101"],
"asset.children": [],
"asset.references": []
"asset.parents": [
"k8s.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"
}
]
}

View file

@ -80,8 +80,8 @@ export async function getAssets({
if (filters.ean) {
musts.push({
term: {
['asset.ean']: filters.ean,
terms: {
['asset.ean']: Array.isArray(filters.ean) ? filters.ean : [filters.ean],
},
});
}

View file

@ -8,27 +8,43 @@
import { schema } from '@kbn/config-schema';
import { RequestHandlerContext } from '@kbn/core/server';
import { debug } from '../../common/debug_log';
import { AssetFilters } from '../../common/types_api';
import { ASSET_MANAGER_API_BASE } from '../constants';
import { getAssets } from '../lib/get_assets';
import { SetupRouteOptions } from './types';
import { getEsClientFromContext } from './utils';
export type GetAssetsQueryOptions = AssetFilters & {
size?: number;
};
const assetType = schema.oneOf([
schema.literal('k8s.pod'),
schema.literal('k8s.cluster'),
schema.literal('k8s.node'),
]);
const getAssetsQueryOptions = schema.object({
from: schema.maybe(schema.string()),
to: schema.maybe(schema.string()),
type: schema.maybe(schema.oneOf([schema.arrayOf(assetType), assetType])),
ean: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])),
size: schema.maybe(schema.number()),
});
export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupRouteOptions<T>) {
// GET assets
router.get<unknown, GetAssetsQueryOptions | undefined, unknown>(
// GET /assets
router.get<unknown, typeof getAssetsQueryOptions.type, unknown>(
{
path: `${ASSET_MANAGER_API_BASE}/assets`,
validate: {
query: schema.any({}),
query: getAssetsQueryOptions,
},
},
async (context, req, res) => {
const { size, ...filters } = req.query || {};
if (filters.type && filters.ean) {
return res.badRequest({
body: 'Filters "type" and "ean" are mutually exclusive but found both.',
});
}
const esClient = await getEsClientFromContext(context);
try {

View file

@ -122,6 +122,62 @@ export default function ({ getService }: FtrProviderContext) {
expect(getResponse.body).to.have.property('results');
expect(getResponse.body.results.length).to.equal(samplesForFilteredTypes.length);
});
it('should reject requests that try to filter by both type and ean', async () => {
const sampleType = sampleAssetDocs[0]['asset.type'];
const sampleEan = sampleAssetDocs[0]['asset.ean'];
const getResponse = await supertest
.get(ASSETS_ENDPOINT)
.query({ type: sampleType, ean: sampleEan })
.expect(400);
expect(getResponse.body.message).to.equal(
'Filters "type" and "ean" are mutually exclusive but found both.'
);
});
it('should return the asset matching a single ean', async () => {
await createSampleAssets(supertest);
const targetAsset = sampleAssetDocs[0];
const singleSampleEan = targetAsset['asset.ean'];
const getResponse = await supertest
.get(ASSETS_ENDPOINT)
.query({ size: 5, from: 'now-1d', ean: singleSampleEan })
.expect(200);
expect(getResponse.body).to.have.property('results');
expect(getResponse.body.results.length).to.equal(1);
const returnedAsset = getResponse.body.results[0];
delete returnedAsset['@timestamp'];
expect(returnedAsset).to.eql(targetAsset);
});
it('should return assets matching multiple eans', async () => {
await createSampleAssets(supertest);
const targetAssets = [sampleAssetDocs[0], sampleAssetDocs[2], sampleAssetDocs[4]];
const sampleEans = targetAssets.map((asset) => asset['asset.ean']);
sampleEans.push('ean-that-does-not-exist');
const getResponse = await supertest
.get(ASSETS_ENDPOINT)
.query({ size: 5, from: 'now-1d', ean: sampleEans })
.expect(200);
expect(getResponse.body).to.have.property('results');
expect(getResponse.body.results.length).to.equal(3);
delete getResponse.body.results[0]['@timestamp'];
delete getResponse.body.results[1]['@timestamp'];
delete getResponse.body.results[2]['@timestamp'];
// The order of the expected assets is fixed
expect(getResponse.body.results).to.eql(targetAssets);
});
});
});
}