Skip to content

Commit

Permalink
[Logs onboarding] Expose agent latest available version from fleet (e…
Browse files Browse the repository at this point in the history
…lastic#166811)

Relates to elastic#165657.

In elastic#166150 a method that returns
the latest available version was introduced in fleet.

We would like to use this method as a consumers of fleet plugin
([observability_onboarding](https://github.com/elastic/kibana/tree/main/x-pack/plugins/observability_onboarding)).
Additionally we would like to decide from outside fleet wether we want
to include currentVersion or not, this is specially useful for us since
we download the elastic agent executable from
https://artifacts.elastic.co/downloads/beats/elastic-agent/ where
snapshots are not available.
  • Loading branch information
yngrdyn authored Sep 21, 2023
1 parent 3709e77 commit 39aebd6
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 14 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ function isStringArray(arr: unknown | string[]): arr is string[] {

export const getAvailableVersionsHandler: RequestHandler = async (context, request, response) => {
try {
const availableVersions = await AgentService.getAvailableVersions();
const availableVersions = await AgentService.getAvailableVersions({});
const body: GetAvailableVersionsResponse = { items: availableVersions };
return response.ok({ body });
} catch (error) {
Expand Down
26 changes: 24 additions & 2 deletions x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,12 @@ export const getFullAgentPolicy: FleetRequestHandler<

if (request.query.kubernetes === true) {
try {
const agentVersion =
await fleetContext.agentClient.asInternalUser.getLatestAgentAvailableVersion();
const fullAgentConfigMap = await agentPolicyService.getFullAgentConfigMap(
soClient,
request.params.agentPolicyId,
agentVersion,
{ standalone: request.query.standalone === true }
);
if (fullAgentConfigMap) {
Expand Down Expand Up @@ -356,9 +359,12 @@ export const downloadFullAgentPolicy: FleetRequestHandler<

if (request.query.kubernetes === true) {
try {
const agentVersion =
await fleetContext.agentClient.asInternalUser.getLatestAgentAvailableVersion();
const fullAgentConfigMap = await agentPolicyService.getFullAgentConfigMap(
soClient,
request.params.agentPolicyId,
agentVersion,
{ standalone: request.query.standalone === true }
);
if (fullAgentConfigMap) {
Expand Down Expand Up @@ -411,10 +417,18 @@ 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 fullAgentManifest = await agentPolicyService.getFullAgentManifest(fleetServer, token);
const agentVersion =
await fleetContext.agentClient.asInternalUser.getLatestAgentAvailableVersion();
const fullAgentManifest = await agentPolicyService.getFullAgentManifest(
fleetServer,
token,
agentVersion
);
if (fullAgentManifest) {
const body: GetFullAgentManifestResponse = {
item: fullAgentManifest,
Expand All @@ -437,10 +451,18 @@ 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 fullAgentManifest = await agentPolicyService.getFullAgentManifest(fleetServer, token);
const agentVersion =
await fleetContext.agentClient.asInternalUser.getLatestAgentAvailableVersion();
const fullAgentManifest = await agentPolicyService.getFullAgentManifest(
fleetServer,
token,
agentVersion
);
if (fullAgentManifest) {
const body = fullAgentManifest;
const headers: ResponseHeaders = {
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import {
} from './elastic_agent_manifest';

import { bulkInstallPackages } from './epm/packages';
import { getAgentsByKuery, getLatestAvailableVersion } from './agents';
import { getAgentsByKuery } from './agents';
import { packagePolicyService } from './package_policy';
import { incrementPackagePolicyCopyName } from './package_policies';
import { outputService } from './output';
Expand Down Expand Up @@ -1029,6 +1029,7 @@ class AgentPolicyService {
public async getFullAgentConfigMap(
soClient: SavedObjectsClientContract,
id: string,
agentVersion: string,
options?: { standalone: boolean }
): Promise<string | null> {
const fullAgentPolicy = await getFullAgentPolicy(soClient, id, options);
Expand All @@ -1048,7 +1049,6 @@ class AgentPolicyService {
},
};

const agentVersion = await getLatestAvailableVersion();
const configMapYaml = fullAgentConfigMapToYaml(fullAgentConfigMap, safeDump);
const updateManifestVersion = elasticAgentStandaloneManifest.replace('VERSION', agentVersion);
const fixedAgentYML = configMapYaml.replace('agent.yml:', 'agent.yml: |-');
Expand All @@ -1060,9 +1060,9 @@ class AgentPolicyService {

public async getFullAgentManifest(
fleetServer: string,
enrolToken: string
enrolToken: string,
agentVersion: string
): Promise<string | null> {
const agentVersion = await getLatestAvailableVersion();
const updateManifestVersion = elasticAgentManagedManifest.replace('VERSION', agentVersion);
let updateManifest = updateManifestVersion;
if (fleetServer !== '') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const createClientMock = (): jest.Mocked<AgentClient> => ({
getAgentStatusById: jest.fn(),
getAgentStatusForAgentPolicy: jest.fn(),
listAgents: jest.fn(),
getLatestAgentAvailableVersion: jest.fn(),
});

const createServiceMock = (): jest.Mocked<AgentService> => ({
Expand Down
19 changes: 19 additions & 0 deletions x-pack/plugins/fleet/server/services/agents/agent_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
jest.mock('../security');
jest.mock('./crud');
jest.mock('./status');
jest.mock('./versions');

import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import {
Expand All @@ -25,12 +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';

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;

describe('AgentService', () => {
beforeEach(() => {
Expand Down Expand Up @@ -100,6 +103,14 @@ describe('AgentService', () => {
)
);
});

it('rejects on getLatestAgentAvailableVersion', async () => {
await expect(agentClient.getLatestAgentAvailableVersion()).rejects.toThrowError(
new FleetUnauthorizedError(
`User does not have adequate permissions to access Fleet agents.`
)
);
});
});

describe('with required privilege', () => {
Expand Down Expand Up @@ -188,4 +199,12 @@ function expectApisToCallServicesSuccessfully(
'foo-filter'
);
});

test('client.getLatestAgentAvailableVersion calls getLatestAvailableVersion and returns results', async () => {
mockGetLatestAvailableVersion.mockResolvedValue('getLatestAvailableVersion success');
await expect(agentClient.getLatestAgentAvailableVersion()).resolves.toEqual(
'getLatestAvailableVersion success'
);
expect(mockGetLatestAvailableVersion).toHaveBeenCalledTimes(1);
});
}
11 changes: 11 additions & 0 deletions x-pack/plugins/fleet/server/services/agents/agent_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { FleetUnauthorizedError } from '../../errors';

import { getAgentsByKuery, getAgentById } from './crud';
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
import { getLatestAvailableVersion } from './versions';

/**
* A service for interacting with Agent data. See {@link AgentClient} for more information.
Expand Down Expand Up @@ -78,6 +79,11 @@ export interface AgentClient {
page: number;
perPage: number;
}>;

/**
* Return the latest agent available version
*/
getLatestAgentAvailableVersion(includeCurrentVersion?: boolean): Promise<string>;
}

/**
Expand Down Expand Up @@ -119,6 +125,11 @@ class AgentClientImpl implements AgentClient {
);
}

public async getLatestAgentAvailableVersion(includeCurrentVersion?: boolean) {
await this.#runPreflight();
return getLatestAvailableVersion(includeCurrentVersion);
}

#runPreflight = async () => {
if (this.preflightCheck) {
return this.preflightCheck();
Expand Down
15 changes: 12 additions & 3 deletions x-pack/plugins/fleet/server/services/agents/versions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('getAvailableVersions', () => {
mockKibanaVersion = '300.0.0';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);

const res = await getAvailableVersions(false);
const res = await getAvailableVersions({ cached: false });

expect(res).toEqual(['300.0.0', '8.1.0', '8.0.0', '7.17.0']);
});
Expand All @@ -39,7 +39,7 @@ describe('getAvailableVersions', () => {
mockKibanaVersion = '300.0.0-SNAPSHOT';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);

const res = await getAvailableVersions(false);
const res = await getAvailableVersions({ cached: false });
expect(res).toEqual(['300.0.0-SNAPSHOT', '8.1.0', '8.0.0', '7.17.0']);
});

Expand All @@ -52,7 +52,16 @@ describe('getAvailableVersions', () => {
};
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);

const res = await getAvailableVersions(false);
const res = await getAvailableVersions({ cached: false });

expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
});

it('should not include the current version if includeCurrentVersion = false', async () => {
mockKibanaVersion = '300.0.0-SNAPSHOT';
mockedReadFile.mockResolvedValue(`["8.1.0", "8.0.0", "7.17.0", "7.16.0"]`);

const res = await getAvailableVersions({ cached: false, includeCurrentVersion: false });

expect(res).toEqual(['8.1.0', '8.0.0', '7.17.0']);
});
Expand Down
20 changes: 16 additions & 4 deletions x-pack/plugins/fleet/server/services/agents/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,21 @@ const AGENT_VERSION_BUILD_FILE = 'x-pack/plugins/fleet/target/agent_versions_lis

let availableVersions: string[] | undefined;

export const getLatestAvailableVersion = async (): Promise<string> => {
const versions = await getAvailableVersions();
export const getLatestAvailableVersion = async (
includeCurrentVersion?: boolean
): Promise<string> => {
const versions = await getAvailableVersions({ includeCurrentVersion });

return versions[0];
};

export const getAvailableVersions = async (cached = true): Promise<string[]> => {
export const getAvailableVersions = async ({
cached = true,
includeCurrentVersion,
}: {
cached?: boolean;
includeCurrentVersion?: boolean;
}): Promise<string[]> => {
// Use cached value to avoid reading from disk each time
if (cached && availableVersions) {
return availableVersions;
Expand All @@ -51,7 +60,10 @@ export const getAvailableVersions = async (cached = true): Promise<string[]> =>
.sort((a: any, b: any) => (semverGt(a, b) ? -1 : 1));
versionsToDisplay = uniq(versions) as string[];

if (!config?.internal?.onlyAllowAgentUpgradeToKnownVersions) {
const appendCurrentVersion =
includeCurrentVersion ?? !config?.internal?.onlyAllowAgentUpgradeToKnownVersions;

if (appendCurrentVersion) {
// Add current version if not already present
const hasCurrentVersion = versionsToDisplay.some((v) => v === kibanaVersion);

Expand Down

0 comments on commit 39aebd6

Please sign in to comment.