Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Logs+] Implement Data Source APIs #156413

Merged
merged 22 commits into from
May 23, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2ee74e5
Add basic plugin scaffolding from generate
Kerry350 Apr 24, 2023
93f6be6
Remove new plugin in favour of Fleet directory
Kerry350 Apr 25, 2023
ece2f3e
Add "Enumerate integrations" API
Kerry350 Apr 26, 2023
f813c19
Add API for querying datasets
Kerry350 Apr 27, 2023
d0af7ed
Add response types and switch to standard current user permission model
Kerry350 May 2, 2023
52fc67a
Remove "fleetAuthz"
Kerry350 May 2, 2023
8cf8a1e
Add top level filtering
Kerry350 May 4, 2023
ed121b5
Make "type" exhaustive
Kerry350 May 9, 2023
a6a2cd1
Change property names to better align with other APIs
Kerry350 May 15, 2023
23ad878
Add fleetAutz back with integrations > read
Kerry350 May 16, 2023
04753ed
Unit tests for getInstalledPackages
Kerry350 May 16, 2023
7ebe01c
Unit tests for getDataStreams
Kerry350 May 17, 2023
77e1869
Add basic integration tests
Kerry350 May 18, 2023
19f3ec5
Merge remote-tracking branch 'upstream/main' into 2653-implement-data…
Kerry350 May 18, 2023
5c5e344
Alter integration tests to have a strict filter
Kerry350 May 22, 2023
c4123d5
Switch name / title around
Kerry350 May 22, 2023
bb0b52b
Merge remote-tracking branch 'upstream/main' into 2653-implement-data…
Kerry350 May 22, 2023
66759b9
Split function down, use internal SO client, and use custom audit
Kerry350 May 22, 2023
e16e998
Expand API integration tests, try to fix leaky state between tests
Kerry350 May 22, 2023
447e26d
Merge remote-tracking branch 'upstream/main' into 2653-implement-data…
Kerry350 May 23, 2023
edc0979
Add force: true flag
Kerry350 May 23, 2023
d17e7a4
Set default value for uncategorisedOnly to false
Kerry350 May 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/common/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@ export const LIMITED_CONCURRENCY_ROUTE_TAG = 'ingest:limited-concurrency';

// EPM API routes
const EPM_PACKAGES_MANY = `${EPM_API_ROOT}/packages`;
const EPM_PACKAGES_INSTALLED = `${EPM_API_ROOT}/packages/installed`;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const EPM_PACKAGES_BULK = `${EPM_PACKAGES_MANY}/_bulk`;
const EPM_PACKAGES_ONE_DEPRECATED = `${EPM_PACKAGES_MANY}/{pkgkey}`;
const EPM_PACKAGES_ONE = `${EPM_PACKAGES_MANY}/{pkgName}/{pkgVersion}`;
export const EPM_API_ROUTES = {
BULK_INSTALL_PATTERN: EPM_PACKAGES_BULK,
LIST_PATTERN: EPM_PACKAGES_MANY,
INSTALLED_LIST_PATTERN: EPM_PACKAGES_INSTALLED,
LIMITED_LIST_PATTERN: `${EPM_PACKAGES_MANY}/limited`,
INFO_PATTERN: EPM_PACKAGES_ONE,
DATA_STREAMS_PATTERN: `${EPM_API_ROOT}/data_streams`,
INSTALL_FROM_REGISTRY_PATTERN: EPM_PACKAGES_ONE,
INSTALL_BY_UPLOAD_PATTERN: EPM_PACKAGES_MANY,
DELETE_PATTERN: EPM_PACKAGES_ONE,
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/common/types/models/data_stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ export interface DataStream {
serviceName: string;
} | null;
}

export type PackageDataStreamTypes = 'logs' | 'metrics' | 'traces' | 'synthetics' | 'profiling';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly not the best naming, or location? But when looking at other Fleet code I could only really find the DATA_STREAM_INDEX_PATTERN constant, but not a type I could reuse.

23 changes: 23 additions & 0 deletions x-pack/plugins/fleet/common/types/rest_spec/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import type { SortResults } from '@elastic/elasticsearch/lib/api/types';

import type {
AssetReference,
CategorySummaryList,
Expand All @@ -13,6 +15,7 @@ import type {
PackageUsageStats,
InstallType,
InstallSource,
EpmPackageInstallStatus,
} from '../models/epm';

export interface GetCategoriesRequest {
Expand Down Expand Up @@ -46,6 +49,26 @@ export interface GetPackagesResponse {
response?: PackageList;
}

interface InstalledPackage {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fairly minimal, and doesn't necessarily align with the advice given, r.e.:

I do think we should make sure the response body is somewhat aligned with the rest of the Fleet API's patterns though for consistency

Extra properties would have required extra queries to other locations (e.g. the registry), which is why this is so minimal.

Copy link
Contributor

@juliaElastic juliaElastic May 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to keep the API response minimal to avoid extra queries, we can add more fields later if needed for other use cases.

name: string;
version: string;
status: EpmPackageInstallStatus;
dataStreams: Array<{
name: string;
title: string;
}>;
}
export interface GetInstalledPackagesResponse {
items: InstalledPackage[];
total: number;
pageAfter?: SortResults;
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
}

export interface GetEpmDataStreamsResponse {
items: Array<{
name: string;
}>;
}
export interface GetLimitedPackagesResponse {
items: string[];
// deprecated in 8.0
Expand Down
52 changes: 52 additions & 0 deletions x-pack/plugins/fleet/server/routes/epm/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@ import type {
GetStatsResponse,
UpdatePackageResponse,
GetVerificationKeyIdResponse,
GetInstalledPackagesResponse,
GetEpmDataStreamsResponse,
} from '../../../common/types';
import type {
GetCategoriesRequestSchema,
GetPackagesRequestSchema,
GetInstalledPackagesRequestSchema,
GetDataStreamsRequestSchema,
GetFileRequestSchema,
GetInfoRequestSchema,
InstallPackageFromRegistryRequestSchema,
Expand All @@ -49,6 +53,7 @@ import {
bulkInstallPackages,
getCategories,
getPackages,
getInstalledPackages,
getFile,
getPackageInfo,
isBulkInstallError,
Expand All @@ -66,6 +71,7 @@ import { getPackageUsageStats } from '../../services/epm/packages/get';
import { updatePackage } from '../../services/epm/packages/update';
import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification';
import type { ReauthorizeTransformRequestSchema } from '../../types';
import { getDataStreams } from '../../services/epm/data_streams';

const CACHE_CONTROL_10_MINUTES_HEADER: HttpResponseOptions['headers'] = {
'cache-control': 'max-age=600',
Expand Down Expand Up @@ -115,6 +121,52 @@ export const getListHandler: FleetRequestHandler<
}
};

export const getInstalledListHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetInstalledPackagesRequestSchema.query>
> = async (context, request, response) => {
try {
const coreContext = await context.core;
const savedObjectsClient = coreContext.savedObjects.client;
const res = await getInstalledPackages({
savedObjectsClient,
...request.query,
});

const body: GetInstalledPackagesResponse = { ...res };

return response.ok({
body,
});
} catch (error) {
return defaultFleetErrorHandler({ error, response });
}
};

export const getDataStreamsHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetDataStreamsRequestSchema.query>
> = async (context, request, response) => {
try {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asCurrentUser;
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
const res = await getDataStreams({
esClient,
...request.query,
});

const body: GetEpmDataStreamsResponse = {
...res,
};

return response.ok({
body,
});
} catch (error) {
return defaultFleetErrorHandler({ error, response });
}
};

export const getLimitedListHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetLimitedPackagesRequestSchema.query>,
Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/fleet/server/routes/epm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { splitPkgKey } from '../../services/epm/registry';
import {
GetCategoriesRequestSchema,
GetPackagesRequestSchema,
GetInstalledPackagesRequestSchema,
GetFileRequestSchema,
GetInfoRequestSchema,
GetInfoRequestSchemaDeprecated,
Expand All @@ -40,11 +41,13 @@ import {
UpdatePackageRequestSchema,
UpdatePackageRequestSchemaDeprecated,
ReauthorizeTransformRequestSchema,
GetDataStreamsRequestSchema,
} from '../../types';

import {
getCategoriesHandler,
getListHandler,
getInstalledListHandler,
getLimitedListHandler,
getFileHandler,
getInfoHandler,
Expand All @@ -56,6 +59,7 @@ import {
updatePackageHandler,
getVerificationKeyIdHandler,
reauthorizeTransformsHandler,
getDataStreamsHandler,
} from './handlers';

const MAX_FILE_SIZE_BYTES = 104857600; // 100MB
Expand Down Expand Up @@ -83,6 +87,14 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
getListHandler
);

router.get(
juliaElastic marked this conversation as resolved.
Show resolved Hide resolved
{
path: EPM_API_ROUTES.INSTALLED_LIST_PATTERN,
validate: GetInstalledPackagesRequestSchema,
},
getInstalledListHandler
);

router.get(
{
path: EPM_API_ROUTES.LIMITED_LIST_PATTERN,
Expand Down Expand Up @@ -201,6 +213,14 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
getVerificationKeyIdHandler
);

router.get(
juliaElastic marked this conversation as resolved.
Show resolved Hide resolved
{
path: EPM_API_ROUTES.DATA_STREAMS_PATTERN,
validate: GetDataStreamsRequestSchema,
},
getDataStreamsHandler
);

// deprecated since 8.0
router.get(
{
Expand Down
47 changes: 47 additions & 0 deletions x-pack/plugins/fleet/server/services/epm/data_streams/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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 { ElasticsearchClient } from '@kbn/core/server';

import type { PackageDataStreamTypes } from '../../../../common/types';

import { dataStreamService } from '../../data_streams';

export async function getDataStreams(options: {
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
esClient: ElasticsearchClient;
type?: PackageDataStreamTypes;
datasetQuery?: string;
sortDirection: 'asc' | 'desc';
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
uncategorisedOnly: boolean;
}) {
const { esClient, type, datasetQuery, uncategorisedOnly, sortDirection } = options;

const allDataStreams = await dataStreamService.getMatchingDataStreams(esClient, {
type: type ? type : '*',
dataset: datasetQuery ? `*${datasetQuery}*` : '*',
});

const filteredDataStreams = uncategorisedOnly
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is the best name? But we need some sort of option that can differentiate between wanting all data streams or only wanting uncategorised ones, this will be important for the work within Discover.

? allDataStreams.filter((stream) => {
return !stream._meta || !stream._meta.managed_by || stream._meta.managed_by !== 'fleet';
})
: allDataStreams;

const mappedDataStreams = filteredDataStreams.map((dataStream) => {
return { name: dataStream.name };
});

const sortedDataStreams = mappedDataStreams.sort((a, b) => {
return a.name.localeCompare(b.name);
});

const dataStreams = sortDirection === 'asc' ? sortedDataStreams : sortedDataStreams.reverse();

return {
items: dataStreams,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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 { getDataStreams } from './get';
92 changes: 92 additions & 0 deletions x-pack/plugins/fleet/server/services/epm/packages/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import semverGte from 'semver/functions/gte';
import type { Logger } from '@kbn/core/server';
import { withSpan } from '@kbn/apm-utils';

import type { SortResults } from '@elastic/elasticsearch/lib/api/types';

import { nodeBuilder } from '@kbn/es-query';

import { buildNode as buildFunctionNode } from '@kbn/es-query/src/kuery/node_types/function';
import { buildNode as buildWildcardNode } from '@kbn/es-query/src/kuery/node_types/wildcard';

import {
installationStatuses,
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
Expand All @@ -20,6 +27,7 @@ import type {
PackageUsageStats,
PackagePolicySOAttributes,
Installable,
PackageDataStreamTypes,
} from '../../../../common/types';
import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants';
import type {
Expand Down Expand Up @@ -142,6 +150,90 @@ export async function getPackages(
return packageListWithoutStatus;
}

export async function getInstalledPackages(options: {
savedObjectsClient: SavedObjectsClientContract;
type?: PackageDataStreamTypes;
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
nameQuery?: string;
pageAfter?: SortResults;
pageSize: number;
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
sortDirection: 'asc' | 'desc';
}) {
const { savedObjectsClient, pageAfter, pageSize, nameQuery, sortDirection, type } = options;

const packageSavedObjects = await savedObjectsClient.find<Installation>({
Kerry350 marked this conversation as resolved.
Show resolved Hide resolved
type: PACKAGES_SAVED_OBJECT_TYPE,
// Pagination
perPage: pageSize,
...(pageAfter && { searchAfter: pageAfter }),
// Sort
sortField: 'name',
sortOrder: sortDirection,
// Name filter
...(nameQuery && { searchFields: ['name'] }),
...(nameQuery && { search: `${nameQuery}* | ${nameQuery}` }),
filter: nodeBuilder.and([
// Filter to installed packages only
nodeBuilder.is(
`${PACKAGES_SAVED_OBJECT_TYPE}.attributes.install_status`,
installationStatuses.Installed
),
// Filter for a "queryable" marker
buildFunctionNode(
'nested',
`${PACKAGES_SAVED_OBJECT_TYPE}.attributes.installed_es`,
nodeBuilder.is('type', 'index_template')
),
// "Type" filter
...(type
? [
buildFunctionNode(
'nested',
`${PACKAGES_SAVED_OBJECT_TYPE}.attributes.installed_es`,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use installed_es here as other properties within the SO aren't mapped, and therefore aren't queryable. We need something top level so that we can take advantage of pagination on the Saved Objects.

nodeBuilder.is('id', buildWildcardNode(`${type}-*`))
),
]
: []),
]),
});

const integrations = packageSavedObjects.saved_objects.map((integrationSavedObject) => {
const {
name,
version,
install_status: installStatus,
es_index_patterns: esIndexPatterns,
} = integrationSavedObject.attributes;

const dataStreams = Object.entries(esIndexPatterns)
.map(([key, value]) => {
return {
name: key,
title: value,
};
})
.filter((stream) => {
if (!type) {
return true;
} else {
return stream.title.startsWith(`${type}-`);
}
});

return {
name,
version,
status: installStatus,
dataStreams,
};
});

return {
items: integrations,
total: packageSavedObjects.total,
pageAfter: packageSavedObjects.saved_objects.at(-1)?.sort, // Enable ability to use searchAfter in subsequent queries
};
}

// Get package names for packages which cannot have more than one package policy on an agent policy
export async function getLimitedPackages(options: {
savedObjectsClient: SavedObjectsClientContract;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export {
getInstallations,
getPackageInfo,
getPackages,
getInstalledPackages,
getLimitedPackages,
} from './get';

Expand Down
Loading