Skip to content

Commit e1123a5

Browse files
juliaElasticclaude
andauthored
[9.2] [Fleet] only auto-install content packages newer than the installed version (#262703)
## Summary Backport of #262509 to 9.2. The auto-install content packages task was using string equality (`!== version`) to decide whether to reinstall a package. This meant any version mismatch would trigger a reinstall, effectively downgrading packages. The fix replaces string equality with `semverGt` to ensure a package is only installed when the registry version is strictly newer than the installed version. <!--BACKPORT [{"author":{"name":"Julia Bardi","email":"90178898+juliaElastic@users.noreply.github.com"},"sourceCommit":{"committedDate":"2026-04-10T14:55:34Z","message":"[Fleet] only auto-install content packages newer than the installed version (#262509)","sha":"3f939ce873bcbba27b7ccfd44093aa2198bcc743"},"sourcePullRequest":{"number":262509,"url":"https://github.com/elastic/kibana/pull/262509"},"sourceBranch":"main","suggestedTargetBranches":["9.2"],"targetPullRequestStates":[{"branch":"9.2","label":"v9.2.9","state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2e69d04 commit e1123a5

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

x-pack/platform/plugins/shared/fleet/server/tasks/auto_install_content_packages_task.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,5 +249,56 @@ describe('AutoInstallContentPackagesTask', () => {
249249
expect(packageClientMock.installPackage).not.toHaveBeenCalled();
250250
expect(dataStreamService.getAllFleetDataStreams).not.toHaveBeenCalled();
251251
});
252+
253+
it('should not downgrade package when a newer prerelease version is installed', async () => {
254+
// Registry has 1.1.0, but a prerelease 2.0.0-preview is already installed.
255+
// The task must not downgrade to 1.1.0.
256+
mockGetInstalledPackages.mockResolvedValue({
257+
items: [
258+
{
259+
name: 'kubernetes_otel',
260+
version: '2.0.0-preview',
261+
},
262+
{
263+
name: 'test_package',
264+
version: '2.0.0-preview',
265+
},
266+
],
267+
});
268+
269+
await runTask();
270+
271+
expect(packageClientMock.installPackage).not.toHaveBeenCalled();
272+
});
273+
274+
it('should not downgrade one package while upgrading another', async () => {
275+
// kubernetes_otel has a prerelease installed (must not downgrade),
276+
// test_package has an older version installed (must upgrade).
277+
mockGetInstalledPackages.mockResolvedValue({
278+
items: [
279+
{
280+
name: 'kubernetes_otel',
281+
version: '2.0.0-preview',
282+
},
283+
{
284+
name: 'test_package',
285+
version: '1.0.0',
286+
},
287+
],
288+
});
289+
290+
await runTask();
291+
292+
expect(packageClientMock.installPackage).toHaveBeenCalledTimes(1);
293+
expect(packageClientMock.installPackage).toHaveBeenCalledWith({
294+
pkgName: 'test_package',
295+
pkgVersion: '1.1.0',
296+
useStreaming: true,
297+
automaticInstall: true,
298+
});
299+
expect(packageClientMock.installPackage).not.toHaveBeenCalledWith(
300+
expect.objectContaining({ pkgName: 'kubernetes_otel' })
301+
);
302+
});
252303
});
253304
});

x-pack/platform/plugins/shared/fleet/server/tasks/auto_install_content_packages_task.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* 2.0.
66
*/
77
import pMap from 'p-map';
8+
import semverGt from 'semver/functions/gt';
89
import {
910
type CoreSetup,
1011
type ElasticsearchClient,
@@ -239,7 +240,8 @@ export class AutoInstallContentPackagesTask {
239240
this.discoveryMap!
240241
).reduce((acc, [dataset, mapValue]) => {
241242
const packages = mapValue.packages.filter(
242-
(pkg) => !installedPackagesMap[pkg.name] || installedPackagesMap[pkg.name] !== pkg.version
243+
(pkg) =>
244+
!installedPackagesMap[pkg.name] || semverGt(pkg.version, installedPackagesMap[pkg.name])
243245
);
244246
if (packages.length > 0) {
245247
acc[dataset] = { packages };
@@ -256,7 +258,8 @@ export class AutoInstallContentPackagesTask {
256258
if (
257259
mapValue.packages.every(
258260
(pkg) =>
259-
installedPackagesMap[pkg.name] && installedPackagesMap[pkg.name] === pkg.version
261+
installedPackagesMap[pkg.name] &&
262+
!semverGt(pkg.version, installedPackagesMap[pkg.name])
260263
)
261264
) {
262265
acc.push(dataset);
@@ -280,7 +283,7 @@ export class AutoInstallContentPackagesTask {
280283

281284
if (hasData) {
282285
for (const { name, version } of packages) {
283-
if (!installedPackagesMap[name] || installedPackagesMap[name] !== version) {
286+
if (!installedPackagesMap[name] || semverGt(version, installedPackagesMap[name])) {
284287
packagesToInstall[name] = version;
285288
}
286289
}

0 commit comments

Comments
 (0)