[asset_manager] Add /assets/diff endpoint (#153730)

This PR adds a `/diff` endpoint that given two time ranges will return
which assets exist only in either time range and which assets exist in
both time ranges.

### How to test
Start up a local ES and Kibana instance and run these commands to setup
the test data:

```curl
curl -X POST http://localhost:5601/ftw/api/asset-manager/assets/sample \
  -u 'elastic:changeme' \
  -H 'kbn-xsrf: xxx' \
  -H 'Content-Type: application/json' \
  -d '{"baseDateTime":"2022-02-07T00:00:00.000Z", "excludeEans": ["k8s.pod:pod-200wwc3","k8s.pod:pod-200naq4","k8s.pod:pod-200ohr5","k8s.pod:pod-200yyx6","k8s.pod:pod-200psd7","k8s.pod:pod-200wmc8","k8s.pod:pod-200ugg9"]}'
```

```curl
curl -X POST http://localhost:5601/ftw/api/asset-manager/assets/sample \
  -u 'elastic:changeme' \
  -H 'kbn-xsrf: xxx' \
  -H 'Content-Type: application/json' \
  -d '{"baseDateTime":"2022-02-07T01:30:00.000Z", "excludeEans": ["k8s.pod:pod-200wwc3","k8s.pod:pod-200naq4", "k8s.pod:pod-200xrg1","k8s.pod:pod-200dfp2"]}'
```

```curl
curl -X POST http://localhost:5601/ftw/api/asset-manager/assets/sample \
  -u 'elastic:changeme' \
  -H 'kbn-xsrf: xxx' \
  -H 'Content-Type: application/json' \
  -d '{"baseDateTime":"2022-02-07T03:00:00.000Z", "excludeEans": ["k8s.cluster:cluster-001","k8s.cluster:cluster-002","k8s.node:node-101","k8s.node:node-102","k8s.node:node-103","k8s.pod:pod-200xrg1","k8s.pod:pod-200dfp2"]}'
```

From there you can test based on the requests described in the
[documentation](063b730c7a/x-pack/plugins/asset_manager/docs/index.md (get-assetsdiff)).

Closes #153489
This commit is contained in:
Milton Hultgren 2023-04-06 11:55:13 +02:00 committed by GitHub
parent b2812f3278
commit 49d8e0e8ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 841 additions and 0 deletions

View file

@ -406,6 +406,638 @@ GET /assets?from=2023-03-25T17:44:44.000Z&to=2023-03-25T18:44:44.000Z&ean=k8s.no
<a name="sample-data" id="sample-data"></a>
### GET /assets/diff
Returns assets found in the two time ranges, split by what occurs in only either or in both.
#### 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 |
#### Responses
<details>
<summary>Request where comparison range is missing assets that are found in the baseline range</summary>
```curl
GET /assets/diff?aFrom=2022-02-07T00:00:00.000Z&aTo=2022-02-07T01:30:00.000Z&bFrom=2022-02-07T01:00:00.000Z&bTo=2022-02-07T02:00:00.000Z
{
"onlyInA": [
{
"@timestamp": "2022-02-07T00:00:00.000Z",
"asset.type": "k8s.pod",
"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-07T00:00:00.000Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200dfp2",
"asset.name": "k8s-pod-200dfp2-aws",
"asset.ean": "k8s.pod:pod-200dfp2",
"asset.parents": [
"k8s.node:node-101"
]
}
],
"onlyInB": [],
"inBoth": [
{
"@timestamp": "2022-02-07T01:30:00.000Z",
"asset.type": "k8s.cluster",
"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-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"asset.type": "k8s.node",
"asset.id": "node-101",
"asset.name": "k8s-node-101-aws",
"asset.ean": "k8s.node:node-101",
"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-07T01:30:00.000Z",
"asset.type": "k8s.node",
"asset.id": "node-102",
"asset.name": "k8s-node-102-aws",
"asset.ean": "k8s.node:node-102",
"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-07T01:30:00.000Z",
"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-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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"
]
}
]
}
```
</details>
<details>
<summary>Request where baseline range is missing assets that are found in the comparison range</summary>
```curl
GET /assets/diff?aFrom=2022-02-07T01:00:00.000Z&aTo=2022-02-07T01:30:00.000Z&bFrom=2022-02-07T01:00:00.000Z&bTo=2022-02-07T03:00:00.000Z
{
"onlyInA": [],
"onlyInB": [
{
"@timestamp": "2022-02-07T03:00:00.000Z",
"asset.type": "k8s.pod",
"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-07T03:00:00.000Z",
"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"
]
}
],
"inBoth": [
{
"@timestamp": "2022-02-07T01:30:00.000Z",
"asset.type": "k8s.cluster",
"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-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"asset.type": "k8s.node",
"asset.id": "node-101",
"asset.name": "k8s-node-101-aws",
"asset.ean": "k8s.node:node-101",
"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-07T01:30:00.000Z",
"asset.type": "k8s.node",
"asset.id": "node-102",
"asset.name": "k8s-node-102-aws",
"asset.ean": "k8s.node:node-102",
"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-07T01:30:00.000Z",
"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-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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"
]
}
]
}
```
</details>
<details>
<summary>Request where each range is missing assets found in the other range</summary>
```curl
GET /assets/diff?aFrom=2022-02-07T00:00:00.000Z&aTo=2022-02-07T01:30:00.000Z&bFrom=2022-02-07T01:00:00.000Z&bTo=2022-02-07T03:00:00.000Z
{
"onlyInA": [
{
"@timestamp": "2022-02-07T00:00:00.000Z",
"asset.type": "k8s.pod",
"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-07T00:00:00.000Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200dfp2",
"asset.name": "k8s-pod-200dfp2-aws",
"asset.ean": "k8s.pod:pod-200dfp2",
"asset.parents": [
"k8s.node:node-101"
]
}
],
"onlyInB": [
{
"@timestamp": "2022-02-07T03:00:00.000Z",
"asset.type": "k8s.pod",
"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-07T03:00:00.000Z",
"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"
]
}
],
"inBoth": [
{
"@timestamp": "2022-02-07T01:30:00.000Z",
"asset.type": "k8s.cluster",
"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-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"asset.type": "k8s.node",
"asset.id": "node-101",
"asset.name": "k8s-node-101-aws",
"asset.ean": "k8s.node:node-101",
"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-07T01:30:00.000Z",
"asset.type": "k8s.node",
"asset.id": "node-102",
"asset.name": "k8s-node-102-aws",
"asset.ean": "k8s.node:node-102",
"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-07T01:30:00.000Z",
"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-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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"
]
}
]
}
```
</details>
<details>
<summary>Request where each range is missing assets found in the other range, but restricted by type</summary>
```curl
GET /assets/diff?aFrom=2022-02-07T00:00:00.000Z&aTo=2022-02-07T01:30:00.000Z&bFrom=2022-02-07T01:00:00.000Z&bTo=2022-02-07T03:00:00.000Z&type=k8s.pod
{
"onlyInA": [
{
"@timestamp": "2022-02-07T00:00:00.000Z",
"asset.type": "k8s.pod",
"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-07T00:00:00.000Z",
"asset.type": "k8s.pod",
"asset.id": "pod-200dfp2",
"asset.name": "k8s-pod-200dfp2-aws",
"asset.ean": "k8s.pod:pod-200dfp2",
"asset.parents": [
"k8s.node:node-101"
]
}
],
"onlyInB": [
{
"@timestamp": "2022-02-07T03:00:00.000Z",
"asset.type": "k8s.pod",
"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-07T03:00:00.000Z",
"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"
]
}
],
"inBoth": [
{
"@timestamp": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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": "2022-02-07T01:30:00.000Z",
"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"
]
}
]
}
```
</details>
#### GET /assets/sample
Returns the list of pre-defined sample asset documents that would be indexed

View file

@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { RequestHandlerContext } from '@kbn/core/server';
import { differenceBy, intersectionBy } from 'lodash';
import { debug } from '../../common/debug_log';
import { ASSET_MANAGER_API_BASE } from '../constants';
import { getAssets } from '../lib/get_assets';
@ -56,4 +57,79 @@ export function assetsRoutes<T extends RequestHandlerContext>({ router }: SetupR
}
}
);
// GET /assets/diff
const assetType = schema.oneOf([
schema.literal('k8s.pod'),
schema.literal('k8s.cluster'),
schema.literal('k8s.node'),
]);
const getAssetsDiffQueryOptions = schema.object({
aFrom: schema.string(),
aTo: schema.string(),
bFrom: schema.string(),
bTo: schema.string(),
type: schema.maybe(schema.oneOf([schema.arrayOf(assetType), assetType])),
});
router.get<unknown, typeof getAssetsDiffQueryOptions.type, unknown>(
{
path: `${ASSET_MANAGER_API_BASE}/assets/diff`,
validate: {
query: getAssetsDiffQueryOptions,
},
},
async (context, req, res) => {
const { aFrom, aTo, bFrom, bTo, type } = req.query;
if (new Date(aFrom) > new Date(aTo)) {
return res.badRequest({
body: `Time range cannot move backwards in time. "aTo" (${aTo}) is before "aFrom" (${aFrom}).`,
});
}
if (new Date(bFrom) > new Date(bTo)) {
return res.badRequest({
body: `Time range cannot move backwards in time. "bTo" (${bTo}) is before "bFrom" (${bFrom}).`,
});
}
const esClient = await getEsClientFromContext(context);
try {
const resultsForA = await getAssets({
esClient,
filters: {
from: aFrom,
to: aTo,
type,
},
});
const resultsForB = await getAssets({
esClient,
filters: {
from: bFrom,
to: bTo,
type,
},
});
const onlyInA = differenceBy(resultsForA, resultsForB, 'asset.ean');
const onlyInB = differenceBy(resultsForB, resultsForA, 'asset.ean');
const inBoth = intersectionBy(resultsForA, resultsForB, 'asset.ean');
return res.ok({
body: {
onlyInA,
onlyInB,
inBoth,
},
});
} catch (error: unknown) {
debug('error looking up asset records', error);
return res.customError({ statusCode: 500 });
}
}
);
}

View file

@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
import { createSampleAssets, deleteSampleAssets, viewSampleAssetDocs } from '../helpers';
const ASSETS_ENDPOINT = '/api/asset-manager/assets';
const DIFF_ENDPOINT = ASSETS_ENDPOINT + '/diff';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
@ -179,5 +180,137 @@ export default function ({ getService }: FtrProviderContext) {
expect(getResponse.body.results).to.eql(targetAssets);
});
});
describe('GET /assets/diff', () => {
it('should reject requests that do not include the two time ranges to compare', async () => {
const timestamp = new Date().toISOString();
let getResponse = await supertest.get(DIFF_ENDPOINT).expect(400);
expect(getResponse.body.message).to.equal(
'[request query.aFrom]: expected value of type [string] but got [undefined]'
);
getResponse = await supertest.get(DIFF_ENDPOINT).query({ aFrom: timestamp }).expect(400);
expect(getResponse.body.message).to.equal(
'[request query.aTo]: expected value of type [string] but got [undefined]'
);
getResponse = await supertest
.get(DIFF_ENDPOINT)
.query({ aFrom: timestamp, aTo: timestamp })
.expect(400);
expect(getResponse.body.message).to.equal(
'[request query.bFrom]: expected value of type [string] but got [undefined]'
);
getResponse = await supertest
.get(DIFF_ENDPOINT)
.query({ aFrom: timestamp, aTo: timestamp, bFrom: timestamp })
.expect(400);
expect(getResponse.body.message).to.equal(
'[request query.bTo]: expected value of type [string] but got [undefined]'
);
await supertest
.get(DIFF_ENDPOINT)
.query({ aFrom: timestamp, aTo: timestamp, bFrom: timestamp, bTo: timestamp })
.expect(200);
});
it('should reject requests where either time range is moving backwards in time', async () => {
const now = new Date();
const isoNow = now.toISOString();
const oneHourAgo = new Date(now.getTime() - 1000 * 60 * 60 * 1).toISOString();
let getResponse = await supertest
.get(DIFF_ENDPOINT)
.query({
aFrom: isoNow,
aTo: oneHourAgo,
bFrom: isoNow,
bTo: isoNow,
})
.expect(400);
expect(getResponse.body.message).to.equal(
`Time range cannot move backwards in time. "aTo" (${oneHourAgo}) is before "aFrom" (${isoNow}).`
);
getResponse = await supertest
.get(DIFF_ENDPOINT)
.query({
aFrom: isoNow,
aTo: isoNow,
bFrom: isoNow,
bTo: oneHourAgo,
})
.expect(400);
expect(getResponse.body.message).to.equal(
`Time range cannot move backwards in time. "bTo" (${oneHourAgo}) is before "bFrom" (${isoNow}).`
);
await supertest
.get(DIFF_ENDPOINT)
.query({
aFrom: oneHourAgo,
aTo: isoNow,
bFrom: oneHourAgo,
bTo: isoNow,
})
.expect(200);
});
it('should return the difference in assets present between two time ranges', async () => {
const onlyInA = sampleAssetDocs.slice(0, 2);
const onlyInB = sampleAssetDocs.slice(sampleAssetDocs.length - 2);
const inBoth = sampleAssetDocs.slice(2, sampleAssetDocs.length - 2);
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 1000 * 60 * 60 * 1);
const twoHoursAgo = new Date(now.getTime() - 1000 * 60 * 60 * 2);
await createSampleAssets(supertest, {
baseDateTime: twoHoursAgo.toISOString(),
excludeEans: inBoth.concat(onlyInB).map((asset) => asset['asset.ean']),
});
await createSampleAssets(supertest, {
baseDateTime: oneHourAgo.toISOString(),
excludeEans: onlyInA.concat(onlyInB).map((asset) => asset['asset.ean']),
});
await createSampleAssets(supertest, {
excludeEans: inBoth.concat(onlyInA).map((asset) => asset['asset.ean']),
});
const twoHoursAndTenMinuesAgo = new Date(now.getTime() - 1000 * 60 * 130 * 1);
const fiftyMinuesAgo = new Date(now.getTime() - 1000 * 60 * 50 * 1);
const seventyMinuesAgo = new Date(now.getTime() - 1000 * 60 * 70 * 1);
const tenMinutesAfterNow = new Date(now.getTime() + 1000 * 60 * 10);
const getResponse = await supertest
.get(DIFF_ENDPOINT)
.query({
aFrom: twoHoursAndTenMinuesAgo,
aTo: fiftyMinuesAgo,
bFrom: seventyMinuesAgo,
bTo: tenMinutesAfterNow,
})
.expect(200);
expect(getResponse.body).to.have.property('onlyInA');
expect(getResponse.body).to.have.property('onlyInB');
expect(getResponse.body).to.have.property('inBoth');
getResponse.body.onlyInA.forEach((asset: any) => {
delete asset['@timestamp'];
});
getResponse.body.onlyInB.forEach((asset: any) => {
delete asset['@timestamp'];
});
getResponse.body.inBoth.forEach((asset: any) => {
delete asset['@timestamp'];
});
expect(getResponse.body.onlyInA).to.eql(onlyInA);
expect(getResponse.body.onlyInB).to.eql(onlyInB);
expect(getResponse.body.inBoth).to.eql(inBoth);
});
});
});
}