Skip to content

Commit

Permalink
[Fleet] Fix package install with older version (#156257)
Browse files Browse the repository at this point in the history
## Summary

This PR fixes a bug where an older version of a package cannot be
installed when creating a new agent policy.

Closes #152095

### Reproducing the bug

1. Run Kibana off the `main` branch.
2. Install a package of your choice for the first time (it should not
already be installed) on a version lower than the latest (cf. screenshot
1). In the configuration page under `Where to add this integration?`,
leave `New hosts` selected to ensure a new agent policy is created (cf.
screenshot 2).
3. After the package is installed, go to the package settings: notice
that the installed version is actually the latest (cf. screenshot 3).
4. Note: if you go to the `Integration policies` tab of the package, you
will notice that the integration policy has the version number you
intended to install (cf. screenshot 4).

### Cause of the bug

The `onSubmit` hook used by the package install form [makes use the of
the `epm/packages/_bulk`
endpoint](https://github.com/elastic/kibana/blob/e62581f8bf6098f2813768af2f31647c592645a2/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx#L272)
to bulk install multiple packages if a new agent policy is to be
created. The `epm/packages/_bulk` endpoint accepts an array of packages
which are either specified by a package name (string) or name and
version (object). In the current implementation, this hook only provides
the package names, which resolves to the latest version of the package
being installed.

### How this PR fixes the bug

This PR make the following changes:
* Make the `BulkInstallPackagesFromRegistryRequestSchema` validation
accepts an array of either strings or objects of shape `{ name: string,
version: string }` for packages.
* Modify the `sendBulkInstallPackages` request hook to do the same.
* Make the `onSubmit` hook used by the package install form pass the
package name and version to the `epm/packages/_bulk` endpoint.

### Additional changes

* Add API integration test for bulk package install
* Amend OpenAPI definition: [Swagger
link](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/elastic/kibana/a4a3209905a5cf099fa22779e889a57acb124fb2/x-pack/plugins/fleet/common/openapi/bundled.json#/Elastic%20Package%20Manager%20(EPM)/bulk-install-packages)
(cf. screenshot 7)

### Testing steps

1. (Optional) Test that the `epm/packages/_bulk` endpoint accepts
package name as strings or package name and version as objects, or even
a mix of both (cf. screenshot 6).
2. Same steps as `Reproducing the bug` above on this branch. The correct
version of the package should be installed (cf screenshot 5).

### Screenshots

Screenshot 1: preparing to install an older version of a package:
<img width="1917" alt="Screenshot 2023-05-02 at 11 21 12"
src="https://user-images.githubusercontent.com/23701614/235631501-aa03d2b0-6fe8-4e9a-8e0c-5547f8fccea0.png">


Screenshot 2: creating a new agent policy when installing the package:
<img width="1917" alt="Screenshot 2023-05-02 at 11 21 33"
src="https://user-images.githubusercontent.com/23701614/235631650-3d9bfc19-ace3-4488-a7c7-48078aae3e7c.png">

Screenshot 3: integration settings after installing an older version
onto a new agent policy, showing that the installed version is actually
the latest version:
<img width="1917" alt="Screenshot 2023-05-02 at 11 29 07"
src="https://user-images.githubusercontent.com/23701614/235631920-3ca8f606-d225-464e-8094-46aeee4869df.png">


Screenshot 4: `Integration policies` tab, showing that the integration
policy has the older version:
<img width="1917" alt="Screenshot 2023-05-02 at 11 28 43"
src="https://user-images.githubusercontent.com/23701614/235632212-c82a3ddd-cb33-4179-a4ab-b57e33e7490d.png">


Screenshot 5: integration settings after installing an older version
onto a new agent policy **with the bug fix**, showing that the correct
version was installed:
<img width="1917" alt="Screenshot 2023-05-02 at 11 23 09"
src="https://user-images.githubusercontent.com/23701614/235632590-bb26183d-ce60-439c-9bee-eadb9fdb9654.png">


Screenshot 6: API call showing the two ways of specifying a package
using the `epm/packages/_bulk` endpoint (package name only, or name and
version):
<img width="1917" alt="Screenshot 2023-05-02 at 11 59 11"
src="https://user-images.githubusercontent.com/23701614/235637532-ae32072b-ca84-48bb-9760-120a2149f0e5.png">


Screenshot 7: `epm/packages/_bulk` endpoint reference on Swagger:
<img width="1429" alt="Screenshot 2023-05-02 at 15 13 14"
src="https://user-images.githubusercontent.com/23701614/235677504-e8ba1329-1a70-44f8-92c5-0f32e5da7e46.png">

### Checklist

- [x] [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

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
jillguyonnet and kibanamachine authored May 2, 2023
1 parent 30eb413 commit d71f6f3
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 10 deletions.
22 changes: 20 additions & 2 deletions x-pack/plugins/fleet/common/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -581,9 +581,27 @@
"packages": {
"type": "array",
"items": {
"type": "string"
"oneOf": [
{
"type": "string",
"description": "package name"
},
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "package name"
},
"version": {
"type": "string",
"description": "package version"
}
}
}
]
},
"description": "list of package names to install"
"description": "list of packages to install"
},
"force": {
"type": "boolean",
Expand Down
14 changes: 12 additions & 2 deletions x-pack/plugins/fleet/common/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,18 @@ paths:
packages:
type: array
items:
type: string
description: list of package names to install
oneOf:
- type: string
description: package name
- type: object
properties:
name:
type: string
description: package name
version:
type: string
description: package version
description: list of packages to install
force:
type: boolean
description: force install to ignore package verification errors
Expand Down
14 changes: 12 additions & 2 deletions x-pack/plugins/fleet/common/openapi/paths/epm@packages_bulk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,18 @@ post:
packages:
type: array
items:
type: string
description: list of package names to install
oneOf:
- type: string
description: package name
- type: object
properties:
name:
type: string
description: package name
version:
type: string
description: package version
description: list of packages to install
force:
type: boolean
description: force install to ignore package verification errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,9 @@ export function useOnSubmit({
try {
setFormState('LOADING');
if ((withSysMonitoring || newAgentPolicy.monitoring_enabled?.length) ?? 0 > 0) {
const packagesToPreinstall: string[] = [];
const packagesToPreinstall: Array<string | { name: string; version: string }> = [];
if (packageInfo) {
packagesToPreinstall.push(packageInfo.name);
packagesToPreinstall.push({ name: packageInfo.name, version: packageInfo.version });
}
if (withSysMonitoring) {
packagesToPreinstall.push(FLEET_SYSTEM_PACKAGE);
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/fleet/public/hooks/use_request/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ export const sendInstallPackage = (pkgName: string, pkgVersion: string, force: b
});
};

export const sendBulkInstallPackages = (packages: string[]) => {
export const sendBulkInstallPackages = (
packages: Array<string | { name: string; version: string }>
) => {
return sendRequest<InstallPackageResponse, FleetErrorResponse>({
path: epmRouteService.getBulkInstallPath(),
method: 'post',
Expand Down
8 changes: 7 additions & 1 deletion x-pack/plugins/fleet/server/types/rest_spec/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ export const BulkInstallPackagesFromRegistryRequestSchema = {
prerelease: schema.maybe(schema.boolean()),
}),
body: schema.object({
packages: schema.arrayOf(schema.string(), { minSize: 1 }),
packages: schema.arrayOf(
schema.oneOf([
schema.string(),
schema.object({ name: schema.string(), version: schema.string() }),
]),
{ minSize: 1 }
),
force: schema.boolean({ defaultValue: false }),
}),
};
Expand Down
68 changes: 68 additions & 0 deletions x-pack/test/fleet_api_integration/apis/epm/bulk_install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { skipIfNoDockerRegistry } from '../../helpers';
import { setupFleetAndAgents } from '../agents/services';

export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');

const pkgName = 'multiple_versions';
const pkgOlderVersion = '0.1.0';
const pkgLatestVersion = '0.3.0';

const uninstallPackage = async (name: string, version: string) => {
await supertest.delete(`/api/fleet/epm/packages/${name}/${version}`).set('kbn-xsrf', 'xxxx');
};

describe('bulk package install api', async () => {
skipIfNoDockerRegistry(providerContext);
setupFleetAndAgents(providerContext);

it('should install the latest version by default', async () => {
const response = await supertest
.post(`/api/fleet/epm/packages/_bulk?prerelease=true`)
.set('kbn-xsrf', 'xxxx')
.send({ packages: [pkgName] })
.expect(200);

expect(response.body.items.length).equal(1);
expect(response.body.items[0].version).equal(pkgLatestVersion);

await uninstallPackage(pkgName, pkgLatestVersion);
});

it('should install an older version if force is true', async () => {
const response = await supertest
.post(`/api/fleet/epm/packages/_bulk?prerelease=true`)
.set('kbn-xsrf', 'xxxx')
.send({ packages: [{ name: pkgName, version: pkgOlderVersion }], force: true })
.expect(200);

expect(response.body.items.length).equal(1);
expect(response.body.items[0].version).equal(pkgOlderVersion);

await uninstallPackage(pkgName, pkgOlderVersion);
});

it('should reject installing an older version if force is false', async () => {
const response = await supertest
.post(`/api/fleet/epm/packages/_bulk?prerelease=true`)
.set('kbn-xsrf', 'xxxx')
.send({ packages: [{ name: pkgName, version: pkgOlderVersion }] })
.expect(200);

expect(response.body.response[0].statusCode).equal(400);
expect(response.body.response[0].error).equal(
'multiple_versions-0.1.0 is out-of-date and cannot be installed or updated'
);
});
});
}

0 comments on commit d71f6f3

Please sign in to comment.