mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[8.13] [Fleet] Use latest compatible version in K8's manifest instead of latest available version (#179662) (#179876)
# Backport This will backport the following commits from `main` to `8.13`: - [[Fleet] Use latest compatible version in K8's manifest instead of latest available version (#179662)](https://github.com/elastic/kibana/pull/179662) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Kyle Pollich","email":"kyle.pollich@elastic.co"},"sourceCommit":{"committedDate":"2024-04-02T21:49:46Z","message":"[Fleet] Use latest compatible version in K8's manifest instead of latest available version (#179662)\n\nFixes #175149 \r\n\r\n## Summary\r\n\r\nUses the same \"latest compatible version\" logic present in\r\n`useAgentVersion` when we render agent install instructions when we\r\ndisplay the Docker image version for K8's manifests.\r\n\r\n## To test\r\n\r\n1. Create a file at\r\n`x-pack/plugins/fleet/target/agent_versions_list.json` with some newer\r\nversions, e.g.\r\n\r\n```json\r\n[\r\n \"8.16.0\",\r\n \"8.15.0\"\r\n]\r\n```\r\n2. Create an agent policy and add an integration policy for the\r\n`kubernetes` integration\r\n3. Click the `Add Agent` button in the action dropdown for your agent\r\npolicy\r\n4. Observe the `image` value in the displayed K8's manifest has a\r\nversion of `8.13.0` and not one of your newer versions\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"6b29552382cb84f908f02d5dc605b150d1ebbde5","branchLabelMapping":{"^v8.14.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Fleet","backport:prev-minor","ci:project-deploy-security","v8.14.0"],"title":"[Fleet] Use latest compatible version in K8's manifest instead of latest available version","number":179662,"url":"https://github.com/elastic/kibana/pull/179662","mergeCommit":{"message":"[Fleet] Use latest compatible version in K8's manifest instead of latest available version (#179662)\n\nFixes #175149 \r\n\r\n## Summary\r\n\r\nUses the same \"latest compatible version\" logic present in\r\n`useAgentVersion` when we render agent install instructions when we\r\ndisplay the Docker image version for K8's manifests.\r\n\r\n## To test\r\n\r\n1. Create a file at\r\n`x-pack/plugins/fleet/target/agent_versions_list.json` with some newer\r\nversions, e.g.\r\n\r\n```json\r\n[\r\n \"8.16.0\",\r\n \"8.15.0\"\r\n]\r\n```\r\n2. Create an agent policy and add an integration policy for the\r\n`kubernetes` integration\r\n3. Click the `Add Agent` button in the action dropdown for your agent\r\npolicy\r\n4. Observe the `image` value in the displayed K8's manifest has a\r\nversion of `8.13.0` and not one of your newer versions\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"6b29552382cb84f908f02d5dc605b150d1ebbde5"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.14.0","branchLabelMappingKey":"^v8.14.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/179662","number":179662,"mergeCommit":{"message":"[Fleet] Use latest compatible version in K8's manifest instead of latest available version (#179662)\n\nFixes #175149 \r\n\r\n## Summary\r\n\r\nUses the same \"latest compatible version\" logic present in\r\n`useAgentVersion` when we render agent install instructions when we\r\ndisplay the Docker image version for K8's manifests.\r\n\r\n## To test\r\n\r\n1. Create a file at\r\n`x-pack/plugins/fleet/target/agent_versions_list.json` with some newer\r\nversions, e.g.\r\n\r\n```json\r\n[\r\n \"8.16.0\",\r\n \"8.15.0\"\r\n]\r\n```\r\n2. Create an agent policy and add an integration policy for the\r\n`kubernetes` integration\r\n3. Click the `Add Agent` button in the action dropdown for your agent\r\npolicy\r\n4. Observe the `image` value in the displayed K8's manifest has a\r\nversion of `8.13.0` and not one of your newer versions\r\n\r\n\r\n\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"6b29552382cb84f908f02d5dc605b150d1ebbde5"}}]}] BACKPORT--> Co-authored-by: Kyle Pollich <kyle.pollich@elastic.co>
This commit is contained in:
parent
e5027f51e5
commit
5d0cb6d742
12 changed files with 142 additions and 33 deletions
|
@ -33,7 +33,7 @@ import { getAgentById } from '../../services/agents';
|
|||
import type { Agent } from '../../types';
|
||||
|
||||
import { getAllFleetServerAgents } from '../../collectors/get_all_fleet_server_agents';
|
||||
import { getLatestAvailableVersion } from '../../services/agents/versions';
|
||||
import { getLatestAvailableAgentVersion } from '../../services/agents/versions';
|
||||
|
||||
export const postAgentUpgradeHandler: RequestHandler<
|
||||
TypeOf<typeof PostAgentUpgradeRequestSchema.params>,
|
||||
|
@ -45,7 +45,7 @@ export const postAgentUpgradeHandler: RequestHandler<
|
|||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||
const { version, source_uri: sourceUri, force, skipRateLimitCheck } = request.body;
|
||||
const kibanaVersion = appContextService.getKibanaVersion();
|
||||
const latestAgentVersion = await getLatestAvailableVersion();
|
||||
const latestAgentVersion = await getLatestAvailableAgentVersion();
|
||||
try {
|
||||
checkKibanaVersion(version, kibanaVersion, force);
|
||||
} catch (err) {
|
||||
|
|
|
@ -19,7 +19,7 @@ import { HTTPAuthorizationHeader } from '../../../common/http_authorization_head
|
|||
|
||||
import { fullAgentPolicyToYaml } from '../../../common/services';
|
||||
import { appContextService, agentPolicyService } from '../../services';
|
||||
import { getAgentsByKuery } from '../../services/agents';
|
||||
import { getAgentsByKuery, getLatestAvailableAgentVersion } from '../../services/agents';
|
||||
import { AGENTS_PREFIX } from '../../constants';
|
||||
import type {
|
||||
GetAgentPoliciesRequestSchema,
|
||||
|
@ -430,13 +430,12 @@ export const getK8sManifest: FleetRequestHandler<
|
|||
undefined,
|
||||
TypeOf<typeof GetK8sManifestRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const fleetContext = await context.fleet;
|
||||
|
||||
try {
|
||||
const fleetServer = request.query.fleetServer ?? '';
|
||||
const token = request.query.enrolToken ?? '';
|
||||
const agentVersion =
|
||||
await fleetContext.agentClient.asInternalUser.getLatestAgentAvailableVersion();
|
||||
|
||||
const agentVersion = await getLatestAvailableAgentVersion();
|
||||
|
||||
const fullAgentManifest = await agentPolicyService.getFullAgentManifest(
|
||||
fleetServer,
|
||||
token,
|
||||
|
@ -464,13 +463,10 @@ export const downloadK8sManifest: FleetRequestHandler<
|
|||
undefined,
|
||||
TypeOf<typeof GetK8sManifestRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const fleetContext = await context.fleet;
|
||||
|
||||
try {
|
||||
const fleetServer = request.query.fleetServer ?? '';
|
||||
const token = request.query.enrolToken ?? '';
|
||||
const agentVersion =
|
||||
await fleetContext.agentClient.asInternalUser.getLatestAgentAvailableVersion();
|
||||
const agentVersion = await getLatestAvailableAgentVersion();
|
||||
const fullAgentManifest = await agentPolicyService.getFullAgentManifest(
|
||||
fleetServer,
|
||||
token,
|
||||
|
|
|
@ -26,14 +26,14 @@ import type { AgentClient } from './agent_service';
|
|||
import { AgentServiceImpl } from './agent_service';
|
||||
import { getAgentsByKuery, getAgentById } from './crud';
|
||||
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
|
||||
import { getLatestAvailableVersion } from './versions';
|
||||
import { getLatestAvailableAgentVersion } from './versions';
|
||||
|
||||
const mockGetAuthzFromRequest = getAuthzFromRequest as jest.Mock<Promise<FleetAuthz>>;
|
||||
const mockGetAgentsByKuery = getAgentsByKuery as jest.Mock;
|
||||
const mockGetAgentById = getAgentById as jest.Mock;
|
||||
const mockGetAgentStatusById = getAgentStatusById as jest.Mock;
|
||||
const mockGetAgentStatusForAgentPolicy = getAgentStatusForAgentPolicy as jest.Mock;
|
||||
const mockGetLatestAvailableVersion = getLatestAvailableVersion as jest.Mock;
|
||||
const mockgetLatestAvailableAgentVersion = getLatestAvailableAgentVersion as jest.Mock;
|
||||
|
||||
describe('AgentService', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -200,11 +200,11 @@ function expectApisToCallServicesSuccessfully(
|
|||
);
|
||||
});
|
||||
|
||||
test('client.getLatestAgentAvailableVersion calls getLatestAvailableVersion and returns results', async () => {
|
||||
mockGetLatestAvailableVersion.mockResolvedValue('getLatestAvailableVersion success');
|
||||
test('client.getLatestAgentAvailableVersion calls getLatestAvailableAgentVersion and returns results', async () => {
|
||||
mockgetLatestAvailableAgentVersion.mockResolvedValue('getLatestAvailableAgentVersion success');
|
||||
await expect(agentClient.getLatestAgentAvailableVersion()).resolves.toEqual(
|
||||
'getLatestAvailableVersion success'
|
||||
'getLatestAvailableAgentVersion success'
|
||||
);
|
||||
expect(mockGetLatestAvailableVersion).toHaveBeenCalledTimes(1);
|
||||
expect(mockgetLatestAvailableAgentVersion).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import { FleetUnauthorizedError } from '../../errors';
|
|||
|
||||
import { getAgentsByKuery, getAgentById } from './crud';
|
||||
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
|
||||
import { getLatestAvailableVersion } from './versions';
|
||||
import { getLatestAvailableAgentVersion } from './versions';
|
||||
|
||||
/**
|
||||
* A service for interacting with Agent data. See {@link AgentClient} for more information.
|
||||
|
@ -139,7 +139,7 @@ class AgentClientImpl implements AgentClient {
|
|||
|
||||
public async getLatestAgentAvailableVersion(includeCurrentVersion?: boolean) {
|
||||
await this.#runPreflight();
|
||||
return getLatestAvailableVersion(includeCurrentVersion);
|
||||
return getLatestAvailableAgentVersion({ includeCurrentVersion });
|
||||
}
|
||||
|
||||
#runPreflight = async () => {
|
||||
|
|
|
@ -33,7 +33,7 @@ jest.mock('./versions', () => {
|
|||
getAvailableVersions: jest
|
||||
.fn()
|
||||
.mockResolvedValue(['8.4.0', '8.5.0', '8.6.0', '8.7.0', '8.8.0']),
|
||||
getLatestAvailableVersion: jest.fn().mockResolvedValue('8.8.0'),
|
||||
getLatestAvailableAgentVersion: jest.fn().mockResolvedValue('8.8.0'),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import { auditLoggingService } from '../audit_logging';
|
|||
import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers';
|
||||
|
||||
import { buildAgentStatusRuntimeField } from './build_status_runtime_field';
|
||||
import { getLatestAvailableVersion } from './versions';
|
||||
import { getLatestAvailableAgentVersion } from './versions';
|
||||
|
||||
const INACTIVE_AGENT_CONDITION = `status:inactive OR status:unenrolled`;
|
||||
const ACTIVE_AGENT_CONDITION = `NOT (${INACTIVE_AGENT_CONDITION})`;
|
||||
|
@ -332,7 +332,7 @@ export async function getAgentsByKuery(
|
|||
// filtering for a range on the version string will not work,
|
||||
// nor does filtering on a flattened field (local_metadata), so filter here
|
||||
if (showUpgradeable) {
|
||||
const latestAgentVersion = await getLatestAvailableVersion();
|
||||
const latestAgentVersion = await getLatestAvailableAgentVersion();
|
||||
// fixing a bug where upgradeable filter was not returning right results https://github.com/elastic/kibana/issues/117329
|
||||
// query all agents, then filter upgradeable, and return the requested page and correct total
|
||||
// if there are more than SO_SEARCH_LIMIT agents, the logic falls back to same as before
|
||||
|
|
|
@ -19,4 +19,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';
|
||||
export { getAvailableVersions, getLatestAvailableAgentVersion } from './versions';
|
||||
|
|
|
@ -20,7 +20,7 @@ jest.mock('./versions', () => {
|
|||
getAvailableVersions: jest
|
||||
.fn()
|
||||
.mockResolvedValue(['8.4.0', '8.5.0', '8.6.0', '8.7.0', '8.8.0']),
|
||||
getLatestAvailableVersion: jest.fn().mockResolvedValue('8.8.0'),
|
||||
getLatestAvailableAgentVersion: jest.fn().mockResolvedValue('8.8.0'),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import { createErrorActionResults, createAgentAction } from './actions';
|
|||
import { getHostedPolicies, isHostedAgent } from './hosted_agent';
|
||||
import { BulkActionTaskType } from './bulk_action_types';
|
||||
import { getCancelledActions } from './action_status';
|
||||
import { getLatestAvailableVersion } from './versions';
|
||||
import { getLatestAvailableAgentVersion } from './versions';
|
||||
|
||||
export class UpgradeActionRunner extends ActionRunner {
|
||||
protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> {
|
||||
|
@ -78,7 +78,7 @@ export async function upgradeBatch(
|
|||
? givenAgents.filter((agent: Agent) => !isHostedAgent(hostedPolicies, agent))
|
||||
: givenAgents;
|
||||
|
||||
const latestAgentVersion = await getLatestAvailableVersion();
|
||||
const latestAgentVersion = await getLatestAvailableAgentVersion();
|
||||
const upgradeableResults = await Promise.allSettled(
|
||||
agentsToCheckUpgradeable.map(async (agent) => {
|
||||
// Filter out agents that are:
|
||||
|
|
|
@ -12,7 +12,7 @@ import type { DeepPartial } from 'utility-types';
|
|||
|
||||
import type { FleetConfigType } from '../../../public/plugin';
|
||||
|
||||
import { getAvailableVersions } from './versions';
|
||||
import { getAvailableVersions, getLatestAvailableAgentVersion } from './versions';
|
||||
|
||||
let mockKibanaVersion = '300.0.0';
|
||||
let mockConfig: DeepPartial<FleetConfigType> = {};
|
||||
|
@ -39,6 +39,91 @@ const emptyResponse = {
|
|||
text: jest.fn().mockResolvedValue(JSON.stringify({})),
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedReadFile.mockReset();
|
||||
mockedFetch.mockReset();
|
||||
});
|
||||
|
||||
describe('getLatestAvailableAgentVersion', () => {
|
||||
it('should return latest available version when aligned with kibana version', async () => {
|
||||
mockKibanaVersion = '8.13.0';
|
||||
mockedReadFile.mockResolvedValue(`["8.13.0", "8.12.2", "8.12.1", "8.12.0"]`);
|
||||
mockedFetch.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
text: jest.fn().mockResolvedValue(
|
||||
JSON.stringify([
|
||||
[
|
||||
{
|
||||
title: 'Elastic Agent 8.13.0',
|
||||
version_number: '8.13.0',
|
||||
},
|
||||
{
|
||||
title: 'Elastic Agent 8.12.2',
|
||||
version_number: '8.12.2',
|
||||
},
|
||||
{
|
||||
title: 'Elastic Agent 8.12.1',
|
||||
version_number: '8.12.1',
|
||||
},
|
||||
{
|
||||
title: 'Elastic Agent 8.12.0',
|
||||
version_number: '8.12.0',
|
||||
},
|
||||
],
|
||||
])
|
||||
),
|
||||
} as any);
|
||||
|
||||
const res = await getLatestAvailableAgentVersion({ ignoreCache: true });
|
||||
|
||||
expect(res).toEqual('8.13.0');
|
||||
});
|
||||
|
||||
it('should return kibana version when kibana version is newer than latest available and API results are empty', async () => {
|
||||
mockKibanaVersion = '8.14.0';
|
||||
mockedReadFile.mockResolvedValue(`["8.13.0", "8.12.2", "8.12.1", "8.12.0"]`);
|
||||
mockedFetch.mockResolvedValueOnce(emptyResponse);
|
||||
|
||||
const res = await getLatestAvailableAgentVersion({ ignoreCache: true });
|
||||
|
||||
expect(res).toEqual('8.14.0');
|
||||
});
|
||||
|
||||
it('should return latest compatible version when kibana version is older than latest available', async () => {
|
||||
mockKibanaVersion = '8.12.2';
|
||||
mockedReadFile.mockResolvedValue(`["8.13.0", "8.12.2", "8.12.1", "8.12.0"]`);
|
||||
mockedFetch.mockResolvedValueOnce({
|
||||
status: 200,
|
||||
text: jest.fn().mockResolvedValue(
|
||||
JSON.stringify([
|
||||
[
|
||||
{
|
||||
title: 'Elastic Agent 8.13.0',
|
||||
version_number: '8.13.0',
|
||||
},
|
||||
{
|
||||
title: 'Elastic Agent 8.12.2',
|
||||
version_number: '8.12.2',
|
||||
},
|
||||
{
|
||||
title: 'Elastic Agent 8.12.1',
|
||||
version_number: '8.12.1',
|
||||
},
|
||||
{
|
||||
title: 'Elastic Agent 8.12.0',
|
||||
version_number: '8.12.0',
|
||||
},
|
||||
],
|
||||
])
|
||||
),
|
||||
} as any);
|
||||
|
||||
const res = await getLatestAvailableAgentVersion({ ignoreCache: true });
|
||||
|
||||
expect(res).toEqual('8.12.2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
beforeEach(() => {
|
||||
mockedReadFile.mockReset();
|
||||
|
|
|
@ -13,10 +13,14 @@ import pRetry from 'p-retry';
|
|||
import { uniq } from 'lodash';
|
||||
import semverGte from 'semver/functions/gte';
|
||||
import semverGt from 'semver/functions/gt';
|
||||
import semverRcompare from 'semver/functions/rcompare';
|
||||
import semverLt from 'semver/functions/lt';
|
||||
import semverCoerce from 'semver/functions/coerce';
|
||||
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
|
||||
import { differsOnlyInPatch } from '../../../common/services';
|
||||
|
||||
import { appContextService } from '..';
|
||||
|
||||
const MINIMUM_SUPPORTED_VERSION = '7.17.0';
|
||||
|
@ -31,12 +35,36 @@ const CACHE_DURATION = 1000 * 60 * 60;
|
|||
let CACHED_AVAILABLE_VERSIONS: string[] | undefined;
|
||||
let LAST_FETCHED: number | undefined;
|
||||
|
||||
export const getLatestAvailableVersion = async (
|
||||
includeCurrentVersion?: boolean
|
||||
): Promise<string> => {
|
||||
const versions = await getAvailableVersions({ includeCurrentVersion });
|
||||
/**
|
||||
* Fetch the latest available version of Elastic Agent that is compatible with the current Kibana version.
|
||||
*
|
||||
* e.g. if the current Kibana version is 8.12.0, and there is an 8.12.2 patch release of agent available,
|
||||
* this function will return "8.12.2".
|
||||
*/
|
||||
export const getLatestAvailableAgentVersion = async ({
|
||||
includeCurrentVersion = false,
|
||||
ignoreCache = false,
|
||||
}: {
|
||||
includeCurrentVersion?: boolean;
|
||||
ignoreCache?: boolean;
|
||||
} = {}): Promise<string> => {
|
||||
const kibanaVersion = appContextService.getKibanaVersion();
|
||||
|
||||
return versions[0];
|
||||
let latestCompatibleVersion;
|
||||
|
||||
const versions = await getAvailableVersions({ includeCurrentVersion, ignoreCache });
|
||||
versions.sort(semverRcompare);
|
||||
|
||||
if (versions && versions.length > 0 && versions.indexOf(kibanaVersion) !== 0) {
|
||||
latestCompatibleVersion =
|
||||
versions.find((version) => {
|
||||
return semverLt(version, kibanaVersion) || differsOnlyInPatch(version, kibanaVersion);
|
||||
}) || versions[0];
|
||||
} else {
|
||||
latestCompatibleVersion = kibanaVersion;
|
||||
}
|
||||
|
||||
return latestCompatibleVersion;
|
||||
};
|
||||
|
||||
export const getAvailableVersions = async ({
|
||||
|
|
|
@ -25,7 +25,7 @@ export interface AgentPolicyServiceInterface {
|
|||
// Agent services
|
||||
export { AgentServiceImpl } from './agents';
|
||||
export type { AgentClient, AgentService } from './agents';
|
||||
export { getAvailableVersions, getLatestAvailableVersion } from './agents';
|
||||
export { getAvailableVersions, getLatestAvailableAgentVersion } from './agents';
|
||||
|
||||
// Saved object services
|
||||
export { agentPolicyService } from './agent_policy';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue