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

[Fleet] Support package capabilities filtering #162435

Merged
3 changes: 3 additions & 0 deletions config/serverless.es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ telemetry.labels.serverless: search

# Alerts config
xpack.actions.enabledActionTypes: ['.email', '.index', '.slack', '.jira', '.webhook', '.teams']

# Fleet specific configuration
xpack.fleet.internal.capabilities: ['enterprise_search', 'serverless_search']
3 changes: 3 additions & 0 deletions config/serverless.oblt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ xpack.fleet.agentIdVerificationEnabled: false
## APM Serverless Onboarding flow
xpack.apm.serverlessOnboarding: true

# Fleet specific configuration
xpack.fleet.internal.capabilities: ['apm', 'uptime', 'observability']

## Required for force installation of APM Package
xpack.fleet.packages:
- name: apm
Expand Down
6 changes: 4 additions & 2 deletions config/serverless.security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ xpack.serverless.plugin.developer.projectSwitcher.currentType: 'security'
# Specify in telemetry the project type
telemetry.labels.serverless: security

# Fleet specific configuration
xpack.fleet.internal.capabilities: ['security']

# Serverless security specific options
xpack.securitySolution.enableExperimental:
- discoverInTimeline

- discoverInTimeline
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface FleetConfigType {
disableProxies: boolean;
fleetServerStandalone: boolean;
activeAgentsSoftLimit?: number;
capabilities: string[];
};
createArtifactsBulkBatchSize?: number;
}
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/fleet/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ export const config: PluginConfigDescriptor = {
min: 0,
})
),
capabilities: schema.arrayOf(
schema.oneOf([
// See package-spec for the list of available capiblities https://github.com/elastic/package-spec/blob/dcc37b652690f8a2bca9cf8a12fc28fd015730a0/spec/integration/manifest.spec.yml#L113
schema.literal('apm'),
schema.literal('enterprise_search'),
schema.literal('observability'),
schema.literal('security'),
schema.literal('serverless_search'),
schema.literal('uptime'),
]),
{ defaultValue: [] }
),
})
),
enabled: schema.boolean({ defaultValue: true }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ describe('_installPackage', () => {
disableILMPolicies: true,
disableProxies: false,
fleetServerStandalone: false,
capabilities: [],
},
})
);
Expand Down Expand Up @@ -176,6 +177,7 @@ describe('_installPackage', () => {
disableProxies: false,
disableILMPolicies: false,
fleetServerStandalone: false,
capabilities: [],
},
})
);
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/fleet/server/services/epm/packages/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import type {
RegistryPackage,
EpmPackageAdditions,
GetCategoriesRequest,
GetPackagesRequest,
} from '../../../../common/types';
import type { Installation, PackageInfo, PackagePolicySOAttributes } from '../../../types';
import {
Expand All @@ -61,7 +62,6 @@ import { getFilteredSearchPackages } from '../filtered_packages';

import { createInstallableFrom } from '.';

export type { SearchParams } from '../registry';
export { getFile } from '../registry';

function nameAsTitle(name: string) {
Expand All @@ -76,7 +76,7 @@ export async function getPackages(
options: {
savedObjectsClient: SavedObjectsClientContract;
excludeInstallStatus?: boolean;
} & Registry.SearchParams
} & GetPackagesRequest['query']
) {
const {
savedObjectsClient,
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/fleet/server/services/epm/packages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { KibanaAssetType } from '../../../types';
import type { AssetType, Installable, Installation } from '../../../types';

export { bulkInstallPackages, isBulkInstallError } from './bulk_install_packages';
export type { SearchParams } from './get';
export {
getCategories,
getFile,
Expand Down
60 changes: 58 additions & 2 deletions x-pack/plugins/fleet/server/services/epm/registry/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';

import { PackageNotFoundError, RegistryResponseError } from '../../../errors';

import * as Archive from '../archive';

import {
Expand All @@ -17,10 +16,13 @@ import {
fetchFindLatestPackageOrThrow,
fetchInfo,
getLicensePath,
fetchCategories,
fetchList,
} from '.';

const mockLoggerFactory = loggingSystemMock.create();
const mockLogger = mockLoggerFactory.get('mock logger');
const mockGetConfig = jest.fn();

const mockGetBundledPackageByName = jest.fn();
const mockFetchUrl = jest.fn();
Expand All @@ -34,7 +36,7 @@ jest.mock('../..', () => ({
getLogger: () => mockLogger,
getKibanaBranch: () => 'main',
getKibanaVersion: () => '99.0.0',
getConfig: () => ({}),
getConfig: () => mockGetConfig(),
getIsProductionMode: () => false,
},
}));
Expand Down Expand Up @@ -219,3 +221,57 @@ describe('fetchInfo', () => {
}
});
});

describe('fetchCategories', () => {
beforeEach(() => {
mockFetchUrl.mockReset();
mockGetConfig.mockReset();
});
it('call registry with capabilities if configured', async () => {
mockGetConfig.mockReturnValue({
internal: {
capabilities: ['apm', 'security'],
},
});
mockFetchUrl.mockResolvedValue(JSON.stringify([]));
await fetchCategories();
expect(mockFetchUrl).toBeCalledTimes(1);
const callUrl = new URL(mockFetchUrl.mock.calls[0][0]);
expect(callUrl.searchParams.get('capabilities')).toBe('apm,security');
});
it('does not call registry with capabilities if none are configured', async () => {
mockGetConfig.mockReturnValue({});
mockFetchUrl.mockResolvedValue(JSON.stringify([]));
await fetchCategories();
expect(mockFetchUrl).toBeCalledTimes(1);
const callUrl = new URL(mockFetchUrl.mock.calls[0][0]);
expect(callUrl.searchParams.get('capabilities')).toBeNull();
});
});

describe('fetchList', () => {
beforeEach(() => {
mockFetchUrl.mockReset();
mockGetConfig.mockReset();
});
it('call registry with capabilities if configured', async () => {
mockGetConfig.mockReturnValue({
internal: {
capabilities: ['apm', 'security'],
},
});
mockFetchUrl.mockResolvedValue(JSON.stringify([]));
await fetchList();
expect(mockFetchUrl).toBeCalledTimes(1);
const callUrl = new URL(mockFetchUrl.mock.calls[0][0]);
expect(callUrl.searchParams.get('capabilities')).toBe('apm,security');
});
it('does not call registry with capabilities if none are configured', async () => {
mockGetConfig.mockReturnValue({});
mockFetchUrl.mockResolvedValue(JSON.stringify([]));
await fetchList();
expect(mockFetchUrl).toBeCalledTimes(1);
const callUrl = new URL(mockFetchUrl.mock.calls[0][0]);
expect(callUrl.searchParams.get('capabilities')).toBeNull();
});
});
23 changes: 14 additions & 9 deletions x-pack/plugins/fleet/server/services/epm/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import { splitPkgKey as split } from '../../../../common/services';
import { KibanaAssetType } from '../../../types';
import type {
AssetsGroupedByServiceByType,
CategoryId,
CategorySummaryList,
RegistryPackage,
RegistrySearchResults,
GetCategoriesRequest,
GetPackagesRequest,
PackageVerificationResult,
ArchivePackage,
BundledPackage,
Expand Down Expand Up @@ -54,19 +54,14 @@ import { verifyPackageArchiveSignature } from '../packages/package_verification'
import { fetchUrl, getResponse, getResponseStream } from './requests';
import { getRegistryUrl } from './registry_url';

export interface SearchParams {
category?: CategoryId;
prerelease?: boolean;
// deprecated
experimental?: boolean;
}

export const splitPkgKey = split;

export const pkgToPkgKey = ({ name, version }: { name: string; version: string }) =>
`${name}-${version}`;

export async function fetchList(params?: SearchParams): Promise<RegistrySearchResults> {
export async function fetchList(
params?: GetPackagesRequest['query']
): Promise<RegistrySearchResults> {
const registryUrl = getRegistryUrl();
const url = new URL(`${registryUrl}/search`);
if (params) {
Expand All @@ -79,6 +74,7 @@ export async function fetchList(params?: SearchParams): Promise<RegistrySearchRe
}

setKibanaVersion(url);
setCapabilities(url);

return fetchUrl(url.toString()).then(JSON.parse);
}
Expand Down Expand Up @@ -111,6 +107,7 @@ async function _fetchFindLatestPackage(

if (!ignoreConstraints) {
setKibanaVersion(url);
setCapabilities(url);
}

try {
Expand Down Expand Up @@ -239,6 +236,13 @@ function setKibanaVersion(url: URL) {
}
}

function setCapabilities(url: URL) {
const capabilities = appContextService.getConfig()?.internal?.capabilities;
if (capabilities && capabilities.length > 0) {
url.searchParams.set('capabilities', capabilities.join(','));
}
}

export async function fetchCategories(
params?: GetCategoriesRequest['query']
): Promise<CategorySummaryList> {
Expand All @@ -254,6 +258,7 @@ export async function fetchCategories(
}

setKibanaVersion(url);
setCapabilities(url);

return fetchUrl(url.toString()).then(JSON.parse);
}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export type {
InstallSource,
InstallResult,
GetCategoriesRequest,
GetPackagesRequest,
DataType,
FleetServerEnrollmentAPIKey,
FleetServerAgent,
Expand Down