Skip to content

Commit 3f939ce

Browse files
authored
[Fleet] only auto-install content packages newer than the installed version (#262509)
## Summary Closes #260677 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. For example, if `2.0.0-preview` was installed and the registry listed `1.4.0` as the latest stable, the task would downgrade to `1.4.0`. The fix replaces string equality with `semverGt` to make sure there are no downgrades happening. A package is now only installed when the registry version is strictly newer than the installed version. To verify: - install prerelease version of a content package ``` POST kbn:/api/fleet/epm/packages/kubernetes_otel/2.0.0-preview5 { "force": true } ``` - wait 10m until the auto install content packages task runs - verify that the kubernetes_otel package is not downgraded ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [ ] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ...
1 parent a1e50bf commit 3f939ce

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 { type CoreSetup, type ElasticsearchClient, type Logger } from '@kbn/core/server';
910
import type {
1011
ConcreteTaskInstance,
@@ -234,7 +235,8 @@ export class AutoInstallContentPackagesTask {
234235
this.discoveryMap!
235236
).reduce((acc, [dataset, mapValue]) => {
236237
const packages = mapValue.packages.filter(
237-
(pkg) => !installedPackagesMap[pkg.name] || installedPackagesMap[pkg.name] !== pkg.version
238+
(pkg) =>
239+
!installedPackagesMap[pkg.name] || semverGt(pkg.version, installedPackagesMap[pkg.name])
238240
);
239241
if (packages.length > 0) {
240242
acc[dataset] = { packages };
@@ -251,7 +253,8 @@ export class AutoInstallContentPackagesTask {
251253
if (
252254
mapValue.packages.every(
253255
(pkg) =>
254-
installedPackagesMap[pkg.name] && installedPackagesMap[pkg.name] === pkg.version
256+
installedPackagesMap[pkg.name] &&
257+
!semverGt(pkg.version, installedPackagesMap[pkg.name])
255258
)
256259
) {
257260
acc.push(dataset);
@@ -275,7 +278,7 @@ export class AutoInstallContentPackagesTask {
275278

276279
if (hasData) {
277280
for (const { name, version } of packages) {
278-
if (!installedPackagesMap[name] || installedPackagesMap[name] !== version) {
281+
if (!installedPackagesMap[name] || semverGt(version, installedPackagesMap[name])) {
279282
packagesToInstall[name] = version;
280283
}
281284
}

0 commit comments

Comments
 (0)