[Serverless] Use latest published version for initial agent download (#166150)

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Josh Dover 2023-09-11 19:28:38 +02:00 committed by GitHub
parent ee92d7dd4e
commit 32d743a9f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 255 additions and 146 deletions

View file

@ -7,10 +7,10 @@
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiText, EuiLink, EuiSteps, EuiSpacer } from '@elastic/eui';
import { EuiText, EuiLink, EuiSteps, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui';
import { Error } from '../../../../../../../components';
import { useKibanaVersion, useStartServices } from '../../../../../../../../../hooks';
import { useStartServices, useAgentVersion } from '../../../../../../../../../hooks';
import { CreatePackagePolicyBottomBar, NotObscuredByBottomBar } from '../..';
import {
@ -40,7 +40,7 @@ export const InstallElasticAgentManagedPageStep: React.FC<InstallAgentPageProps>
const { docLinks } = core;
const link = docLinks.links.fleet.troubleshooting;
const kibanaVersion = useKibanaVersion();
const agentVersion = useAgentVersion();
const [commandCopied, setCommandCopied] = useState(false);
const [applyCommandCopied, setApplyCommandCopied] = useState(false);
@ -66,7 +66,7 @@ export const InstallElasticAgentManagedPageStep: React.FC<InstallAgentPageProps>
apiKey: enrollmentAPIKey.api_key,
fleetProxy,
fleetServerHosts,
kibanaVersion,
agentVersion: agentVersion || '',
});
const steps = [
@ -103,6 +103,10 @@ export const InstallElasticAgentManagedPageStep: React.FC<InstallAgentPageProps>
})
);
if (!agentVersion) {
return <EuiLoadingSpinner />;
}
return (
<>
<EuiText>

View file

@ -10,6 +10,7 @@ import {
EuiButton,
EuiButtonEmpty,
EuiCallOut,
EuiLoadingSpinner,
EuiModal,
EuiModalBody,
EuiModalFooter,
@ -25,7 +26,7 @@ import {
sendGetEnrollmentAPIKeys,
useCreateCloudShellUrl,
useFleetServerHostsForPolicy,
useKibanaVersion,
useAgentVersion,
} from '../../../../../hooks';
import { GoogleCloudShellGuide } from '../../../../../components';
import { ManualInstructions } from '../../../../../../../components/enrollment_instructions';
@ -44,18 +45,22 @@ export const PostInstallGoogleCloudShellModal: React.FunctionComponent<{
})
);
const { fleetServerHosts, fleetProxy } = useFleetServerHostsForPolicy(agentPolicy);
const kibanaVersion = useKibanaVersion();
const agentVersion = useAgentVersion();
const { cloudShellUrl, error, isError, isLoading } = useCreateCloudShellUrl({
enrollmentAPIKey: apyKeysData?.data?.items[0]?.api_key,
packagePolicy,
});
if (!agentVersion) {
return <EuiLoadingSpinner />;
}
const installManagedCommands = ManualInstructions({
apiKey: apyKeysData?.data?.items[0]?.api_key || 'no_key',
fleetServerHosts,
fleetProxy,
kibanaVersion,
});
const { cloudShellUrl, error, isError, isLoading } = useCreateCloudShellUrl({
enrollmentAPIKey: apyKeysData?.data?.items[0]?.api_key,
packagePolicy,
agentVersion,
});
return (

View file

@ -15,6 +15,7 @@ jest.mock('../../hooks', () => {
...jest.requireActual('../../hooks'),
useFleetServerStandalone: jest.fn(),
useAgentEnrollmentFlyoutData: jest.fn(),
useAgentVersion: jest.fn().mockReturnValue('8.1.0'),
};
});

View file

@ -7,7 +7,7 @@
import React, { useState, useMemo, useEffect } from 'react';
import { EuiSteps } from '@elastic/eui';
import { EuiSteps, EuiLoadingSpinner } from '@elastic/eui';
import { safeDump } from 'js-yaml';
import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
@ -21,8 +21,8 @@ import { StandaloneInstructions, ManualInstructions } from '../../enrollment_ins
import {
useGetOneEnrollmentAPIKey,
useStartServices,
useKibanaVersion,
sendGetOneAgentPolicyFull,
useAgentVersion,
} from '../../../hooks';
import type { InstructionProps } from '../types';
@ -59,7 +59,6 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
const { notifications } = core;
const [fullAgentPolicy, setFullAgentPolicy] = useState<FullAgentPolicy | undefined>();
const [yaml, setYaml] = useState<any | undefined>('');
const kibanaVersion = useKibanaVersion();
let downloadLink = '';
@ -123,8 +122,10 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
}
}, [fullAgentPolicy, isK8s]);
const agentVersion = useAgentVersion();
const instructionsSteps = useMemo(() => {
const standaloneInstallCommands = StandaloneInstructions(kibanaVersion);
const standaloneInstallCommands = StandaloneInstructions(agentVersion || '');
const steps: EuiContainedStepProps[] = !agentPolicy
? [
@ -164,7 +165,7 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
return steps;
}, [
kibanaVersion,
agentVersion,
isK8s,
cloudSecurityIntegration,
agentPolicy,
@ -181,6 +182,10 @@ export const StandaloneSteps: React.FunctionComponent<InstructionProps> = ({
setMode,
]);
if (!agentVersion) {
return <EuiLoadingSpinner />;
}
return <EuiSteps steps={instructionsSteps} />;
};
@ -202,7 +207,6 @@ export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
cloudSecurityIntegration,
installedPackagePolicy,
}) => {
const kibanaVersion = useKibanaVersion();
const core = useStartServices();
const { docLinks } = core;
const link = docLinks.links.fleet.troubleshooting;
@ -214,11 +218,13 @@ export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
const enrolledAgentIds = usePollingAgentCount(selectedPolicy?.id || '');
const agentVersion = useAgentVersion();
const installManagedCommands = ManualInstructions({
apiKey: enrollToken,
fleetServerHosts,
fleetProxy,
kibanaVersion,
agentVersion: agentVersion || '',
});
const instructionsSteps = useMemo(() => {
@ -326,5 +332,9 @@ export const ManagedSteps: React.FunctionComponent<InstructionProps> = ({
installedPackagePolicy,
]);
if (!agentVersion) {
return <EuiLoadingSpinner />;
}
return <EuiSteps steps={instructionsSteps} />;
};

View file

@ -27,12 +27,12 @@ export const ManualInstructions = ({
apiKey,
fleetServerHosts,
fleetProxy,
kibanaVersion,
agentVersion: agentVersion,
}: {
apiKey: string;
fleetServerHosts: string[];
fleetProxy?: FleetProxy;
kibanaVersion: string;
agentVersion: string;
}) => {
const enrollArgs = getfleetServerHostsEnrollArgs(apiKey, fleetServerHosts, fleetProxy);
const fleetServerUrl = enrollArgs?.split('--url=')?.pop()?.split('--enrollment')[0];
@ -40,31 +40,31 @@ export const ManualInstructions = ({
const k8sCommand = 'kubectl apply -f elastic-agent-managed-kubernetes.yml';
const linuxCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
cd elastic-agent-${kibanaVersion}-linux-x86_64
const linuxCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-linux-x86_64.tar.gz
tar xzvf elastic-agent-${agentVersion}-linux-x86_64.tar.gz
cd elastic-agent-${agentVersion}-linux-x86_64
sudo ./elastic-agent install ${enrollArgs}`;
const macCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
cd elastic-agent-${kibanaVersion}-darwin-x86_64
const macCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-darwin-x86_64.tar.gz
tar xzvf elastic-agent-${agentVersion}-darwin-x86_64.tar.gz
cd elastic-agent-${agentVersion}-darwin-x86_64
sudo ./elastic-agent install ${enrollArgs}`;
const windowsCommand = `$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-windows-x86_64.zip -OutFile elastic-agent-${kibanaVersion}-windows-x86_64.zip
Expand-Archive .\\elastic-agent-${kibanaVersion}-windows-x86_64.zip -DestinationPath .
cd elastic-agent-${kibanaVersion}-windows-x86_64
Invoke-WebRequest -Uri https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-windows-x86_64.zip -OutFile elastic-agent-${agentVersion}-windows-x86_64.zip
Expand-Archive .\\elastic-agent-${agentVersion}-windows-x86_64.zip -DestinationPath .
cd elastic-agent-${agentVersion}-windows-x86_64
.\\elastic-agent.exe install ${enrollArgs}`;
const linuxDebCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-amd64.deb
sudo dpkg -i elastic-agent-${kibanaVersion}-amd64.deb
const linuxDebCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-amd64.deb
sudo dpkg -i elastic-agent-${agentVersion}-amd64.deb
sudo elastic-agent enroll ${enrollArgs} \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxRpmCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-x86_64.rpm
sudo rpm -vi elastic-agent-${kibanaVersion}-x86_64.rpm
const linuxRpmCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-x86_64.rpm
sudo rpm -vi elastic-agent-${agentVersion}-x86_64.rpm
sudo elastic-agent enroll ${enrollArgs} \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const googleCloudShellCommand = `gcloud config set project <PROJECT_ID> && \nFLEET_URL=${fleetServerUrl} ENROLLMENT_TOKEN=${enrollmentToken} STACK_VERSION=${kibanaVersion} ./deploy.sh`;
const googleCloudShellCommand = `gcloud config set project <PROJECT_ID> && \nFLEET_URL=${fleetServerUrl} ENROLLMENT_TOKEN=${enrollmentToken} STACK_VERSION=${agentVersion} ./deploy.sh`;
return {
linux: linuxCommand,

View file

@ -6,27 +6,27 @@
*/
import type { CommandsByPlatform } from '../../../applications/fleet/components/fleet_server_instructions/utils/install_command_utils';
export const StandaloneInstructions = (kibanaVersion: string): CommandsByPlatform => {
const linuxDebCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-amd64.deb
sudo dpkg -i elastic-agent-${kibanaVersion}-amd64.deb \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
export const StandaloneInstructions = (agentVersion: string): CommandsByPlatform => {
const linuxDebCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-amd64.deb
sudo dpkg -i elastic-agent-${agentVersion}-amd64.deb \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxRpmCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-x86_64.rpm
sudo rpm -vi elastic-agent-${kibanaVersion}-x86_64.rpm \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxRpmCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-x86_64.rpm
sudo rpm -vi elastic-agent-${agentVersion}-x86_64.rpm \nsudo systemctl enable elastic-agent \nsudo systemctl start elastic-agent`;
const linuxCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-linux-x86_64.tar.gz
cd elastic-agent-${kibanaVersion}-linux-x86_64
const linuxCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-linux-x86_64.tar.gz
tar xzvf elastic-agent-${agentVersion}-linux-x86_64.tar.gz
cd elastic-agent-${agentVersion}-linux-x86_64
sudo ./elastic-agent install`;
const macCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
tar xzvf elastic-agent-${kibanaVersion}-darwin-x86_64.tar.gz
cd elastic-agent-${kibanaVersion}-darwin-x86_64
const macCommand = `curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-darwin-x86_64.tar.gz
tar xzvf elastic-agent-${agentVersion}-darwin-x86_64.tar.gz
cd elastic-agent-${agentVersion}-darwin-x86_64
sudo ./elastic-agent install`;
const windowsCommand = `$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${kibanaVersion}-windows-x86_64.zip -OutFile elastic-agent-${kibanaVersion}-windows-x86_64.zip
Expand-Archive .\elastic-agent-${kibanaVersion}-windows-x86_64.zip -DestinationPath .
cd elastic-agent-${kibanaVersion}-windows-x86_64
Invoke-WebRequest -Uri https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-${agentVersion}-windows-x86_64.zip -OutFile elastic-agent-${agentVersion}-windows-x86_64.zip
Expand-Archive .\elastic-agent-${agentVersion}-windows-x86_64.zip -DestinationPath .
cd elastic-agent-${agentVersion}-windows-x86_64
.\\elastic-agent.exe install`;
const k8sCommand = 'kubectl apply -f elastic-agent-standalone-kubernetes.yml';

View file

@ -34,3 +34,4 @@ export * from './use_fleet_server_standalone';
export * from './use_locator';
export * from './use_create_cloud_formation_url';
export * from './use_create_cloud_shell_url';
export * from './use_agent_version';

View file

@ -0,0 +1,37 @@
/*
* 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 { useEffect, useState } from 'react';
import { useKibanaVersion } from './use_kibana_version';
import { sendGetAgentsAvailableVersions } from './use_request';
/**
* @returns The most recent agent version available to install or upgrade to.
*/
export const useAgentVersion = (): string | undefined => {
const kibanaVersion = useKibanaVersion();
const [agentVersion, setAgentVersion] = useState<string | undefined>(undefined);
useEffect(() => {
const getVersions = async () => {
try {
const res = await sendGetAgentsAvailableVersions();
// if the endpoint returns an error, use the fallback versions
const versionsList = res?.data?.items ? res.data.items : [kibanaVersion];
setAgentVersion(versionsList[0]);
} catch (err) {
return;
}
};
getVersions();
}, [kibanaVersion]);
return agentVersion;
};

View file

@ -5,72 +5,30 @@
* 2.0.
*/
import { readFile } from 'fs/promises';
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import { getAvailableVersionsHandler } from './handlers';
let mockKibanaVersion = '300.0.0';
let mockConfig = {};
jest.mock('../../services/agents/versions', () => {
return {
getAvailableVersions: jest.fn().mockReturnValue(['8.1.0', '8.0.0', '7.17.0']),
};
});
jest.mock('../../services/app_context', () => {
const { loggerMock } = jest.requireActual('@kbn/logging-mocks');
return {
appContextService: {
getLogger: () => loggerMock.create(),
getKibanaVersion: () => mockKibanaVersion,
getConfig: () => mockConfig,
},
};
});
jest.mock('fs/promises');
const mockedReadFile = readFile as jest.MockedFunction<typeof readFile>;
describe('getAvailableVersionsHandler', () => {
it('should return available version and filter version < 7.17', async () => {
mockKibanaVersion = '300.0.0';
it('should return the value from getAvailableVersions', async () => {
const ctx = coreMock.createCustomRequestHandlerContext(coreMock.createRequestHandlerContext());
const response = httpServerMock.createResponseFactory();
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
await getAvailableVersionsHandler(ctx, httpServerMock.createKibanaRequest(), response);
expect(response.ok).toBeCalled();
expect(response.ok.mock.calls[0][0]?.body).toEqual({
items: ['300.0.0', '8.1.0', '8.0.0', '7.17.0'],
});
});
it('should not strip -SNAPSHOT from kibana version', async () => {
mockKibanaVersion = '300.0.0-SNAPSHOT';
const ctx = coreMock.createCustomRequestHandlerContext(coreMock.createRequestHandlerContext());
const response = httpServerMock.createResponseFactory();
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
await getAvailableVersionsHandler(ctx, httpServerMock.createKibanaRequest(), response);
expect(response.ok).toBeCalled();
expect(response.ok.mock.calls[0][0]?.body).toEqual({
items: ['300.0.0-SNAPSHOT', '8.1.0', '8.0.0', '7.17.0'],
});
});
it('should not include the current version if onlyAllowAgentUpgradeToKnownVersions = true', async () => {
mockKibanaVersion = '300.0.0-SNAPSHOT';
mockConfig = {
internal: {
onlyAllowAgentUpgradeToKnownVersions: true,
},
};
const ctx = coreMock.createCustomRequestHandlerContext(coreMock.createRequestHandlerContext());
const response = httpServerMock.createResponseFactory();
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
await getAvailableVersionsHandler(ctx, httpServerMock.createKibanaRequest(), response);
expect(response.ok).toBeCalled();

View file

@ -5,21 +5,10 @@
* 2.0.
*/
import { readFile } from 'fs/promises';
import Path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import { uniq } from 'lodash';
import semverGte from 'semver/functions/gte';
import semverGt from 'semver/functions/gt';
import semverCoerce from 'semver/functions/coerce';
import { type RequestHandler, SavedObjectsErrorHelpers } from '@kbn/core/server';
import type { TypeOf } from '@kbn/config-schema';
import { appContextService } from '../../services';
const MINIMUM_SUPPORTED_VERSION = '7.17.0';
import type {
GetAgentsResponse,
GetOneAgentResponse,
@ -363,36 +352,10 @@ function isStringArray(arr: unknown | string[]): arr is string[] {
return Array.isArray(arr) && arr.every((p) => typeof p === 'string');
}
// Read a static file generated at build time
export const getAvailableVersionsHandler: RequestHandler = async (context, request, response) => {
const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_list.json';
const config = await appContextService.getConfig();
let versionsToDisplay: string[] = [];
const kibanaVersion = appContextService.getKibanaVersion();
try {
const file = await readFile(Path.join(REPO_ROOT, AGENT_VERSION_BUILD_FILE), 'utf-8');
// Exclude versions older than MINIMUM_SUPPORTED_VERSION and pre-release versions (SNAPSHOT, rc..)
// De-dup and sort in descending order
const data: string[] = JSON.parse(file);
const versions = data
.map((item: any) => semverCoerce(item)?.version || '')
.filter((v: any) => semverGte(v, MINIMUM_SUPPORTED_VERSION))
.sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1));
versionsToDisplay = uniq(versions) as string[];
if (!config?.internal?.onlyAllowAgentUpgradeToKnownVersions) {
// Add current version if not already present
const hasCurrentVersion = versionsToDisplay.some((v) => v === kibanaVersion);
versionsToDisplay = !hasCurrentVersion
? [kibanaVersion].concat(versionsToDisplay)
: versionsToDisplay;
}
const body: GetAvailableVersionsResponse = { items: versionsToDisplay };
const availableVersions = await AgentService.getAvailableVersions();
const body: GetAvailableVersionsResponse = { items: availableVersions };
return response.ok({ body });
} catch (error) {
return defaultFleetErrorHandler({ error, response });

View file

@ -83,7 +83,7 @@ import {
} from './elastic_agent_manifest';
import { bulkInstallPackages } from './epm/packages';
import { getAgentsByKuery } from './agents';
import { getAgentsByKuery, getLatestAvailableVersion } from './agents';
import { packagePolicyService } from './package_policy';
import { incrementPackagePolicyCopyName } from './package_policies';
import { outputService } from './output';
@ -1048,11 +1048,9 @@ class AgentPolicyService {
},
};
const agentVersion = await getLatestAvailableVersion();
const configMapYaml = fullAgentConfigMapToYaml(fullAgentConfigMap, safeDump);
const updateManifestVersion = elasticAgentStandaloneManifest.replace(
'VERSION',
appContextService.getKibanaVersion()
);
const updateManifestVersion = elasticAgentStandaloneManifest.replace('VERSION', agentVersion);
const fixedAgentYML = configMapYaml.replace('agent.yml:', 'agent.yml: |-');
return [fixedAgentYML, updateManifestVersion].join('\n');
} else {
@ -1064,10 +1062,8 @@ class AgentPolicyService {
fleetServer: string,
enrolToken: string
): Promise<string | null> {
const updateManifestVersion = elasticAgentManagedManifest.replace(
'VERSION',
appContextService.getKibanaVersion()
);
const agentVersion = await getLatestAvailableVersion();
const updateManifestVersion = elasticAgentManagedManifest.replace('VERSION', agentVersion);
let updateManifest = updateManifestVersion;
if (fleetServer !== '') {
updateManifest = updateManifest.replace('https://fleet-server:8220', fleetServer);

View file

@ -20,3 +20,4 @@ export { getAgentUploads, getAgentUploadFile } from './uploads';
export { AgentServiceImpl } from './agent_service';
export type { AgentClient, AgentService } from './agent_service';
export { BulkActionsResolver } from './bulk_actions_resolver';
export { getAvailableVersions, getLatestAvailableVersion } from './versions';

View file

@ -0,0 +1,59 @@
/*
* 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 { readFile } from 'fs/promises';
let mockKibanaVersion = '300.0.0';
let mockConfig = {};
jest.mock('../app_context', () => {
const { loggerMock } = jest.requireActual('@kbn/logging-mocks');
return {
appContextService: {
getLogger: () => loggerMock.create(),
getKibanaVersion: () => mockKibanaVersion,
getConfig: () => mockConfig,
},
};
});
jest.mock('fs/promises');
const mockedReadFile = readFile as jest.MockedFunction<typeof readFile>;
import { getAvailableVersions } from './versions';
describe('getAvailableVersions', () => {
it('should return available version and filter version < 7.17', async () => {
mockKibanaVersion = '300.0.0';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
const res = await getAvailableVersions(false);
expect(res).toEqual(['300.0.0', '8.1.0', '8.0.0', '7.17.0']);
});
it('should not strip -SNAPSHOT from kibana version', async () => {
mockKibanaVersion = '300.0.0-SNAPSHOT';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
const res = await getAvailableVersions(false);
expect(res).toEqual(['300.0.0-SNAPSHOT', '8.1.0', '8.0.0', '7.17.0']);
});
it('should not include the current version if onlyAllowAgentUpgradeToKnownVersions = true', async () => {
mockKibanaVersion = '300.0.0-SNAPSHOT';
mockConfig = {
internal: {
onlyAllowAgentUpgradeToKnownVersions: true,
},
};
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);
const res = await getAvailableVersions(false);
expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
});
});

View file

@ -0,0 +1,73 @@
/*
* 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 { readFile } from 'fs/promises';
import Path from 'path';
import { REPO_ROOT } from '@kbn/repo-info';
import { uniq } from 'lodash';
import semverGte from 'semver/functions/gte';
import semverGt from 'semver/functions/gt';
import semverCoerce from 'semver/functions/coerce';
import { appContextService } from '..';
const MINIMUM_SUPPORTED_VERSION = '7.17.0';
const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_list.json';
let availableVersions: string[] | undefined;
export const getLatestAvailableVersion = async (): Promise<string> => {
const versions = await getAvailableVersions();
return versions[0];
};
export const getAvailableVersions = async (cached = true): Promise<string[]> => {
// Use cached value to avoid reading from disk each time
if (cached && availableVersions) {
return availableVersions;
}
// Read a static file generated at build time
const config = appContextService.getConfig();
let versionsToDisplay: string[] = [];
const kibanaVersion = appContextService.getKibanaVersion();
try {
const file = await readFile(Path.join(REPO_ROOT, AGENT_VERSION_BUILD_FILE), 'utf-8');
// Exclude versions older than MINIMUM_SUPPORTED_VERSION and pre-release versions (SNAPSHOT, rc..)
// De-dup and sort in descending order
const data: string[] = JSON.parse(file);
const versions = data
.map((item: any) => semverCoerce(item)?.version || '')
.filter((v: any) => semverGte(v, MINIMUM_SUPPORTED_VERSION))
.sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1));
versionsToDisplay = uniq(versions) as string[];
if (!config?.internal?.onlyAllowAgentUpgradeToKnownVersions) {
// Add current version if not already present
const hasCurrentVersion = versionsToDisplay.some((v) => v === kibanaVersion);
versionsToDisplay = !hasCurrentVersion
? [kibanaVersion].concat(versionsToDisplay)
: versionsToDisplay;
}
availableVersions = versionsToDisplay;
return availableVersions;
} catch (e) {
if (e.code === 'ENOENT' && !config?.internal?.onlyAllowAgentUpgradeToKnownVersions) {
// If the file does not exist, return the current version
return [kibanaVersion];
}
throw e;
}
};

View file

@ -38,6 +38,7 @@ export interface AgentPolicyServiceInterface {
// Agent services
export { AgentServiceImpl } from './agents';
export type { AgentClient, AgentService } from './agents';
export { getAvailableVersions, getLatestAvailableVersion } from './agents';
// Saved object services
export { agentPolicyService } from './agent_policy';