Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Observability Onboarding] Fix kustomize command #199758

Merged
merged 8 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const createClientMock = (): jest.Mocked<AgentClient> => ({
getAgentStatusForAgentPolicy: jest.fn(),
listAgents: jest.fn(),
getLatestAgentAvailableVersion: jest.fn(),
getLatestAgentAvailableBaseVersion: jest.fn(),
getLatestAgentAvailableDockerImageVersion: jest.fn(),
getByIds: jest.fn(async (..._) => []),
});

Expand Down
24 changes: 24 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 @@ -267,4 +267,28 @@ function expectApisToCallServicesSuccessfully(
);
expect(mockgetLatestAvailableAgentVersion).toHaveBeenCalledTimes(1);
});

test('client.getLatestAgentAvailableBaseVersion strips away IAR suffix', async () => {
mockgetLatestAvailableAgentVersion.mockResolvedValue('1.2.3+build12345678987654321');
await expect(agentClient.getLatestAgentAvailableBaseVersion()).resolves.toEqual('1.2.3');
});

test('client.getLatestAgentAvailableBaseVersion does not break on usual version numbers', async () => {
mockgetLatestAvailableAgentVersion.mockResolvedValue('8.17.0');
await expect(agentClient.getLatestAgentAvailableBaseVersion()).resolves.toEqual('8.17.0');
});

test('client.getLatestAgentAvailableDockerImageVersion transforms IAR suffix', async () => {
mockgetLatestAvailableAgentVersion.mockResolvedValue('1.2.3+build12345678987654321');
await expect(agentClient.getLatestAgentAvailableDockerImageVersion()).resolves.toEqual(
'1.2.3.build12345678987654321'
);
});

test('client.getLatestAgentAvailableDockerImageVersion does not break on usual version numbers', async () => {
mockgetLatestAvailableAgentVersion.mockResolvedValue('8.17.0');
await expect(agentClient.getLatestAgentAvailableDockerImageVersion()).resolves.toEqual(
'8.17.0'
);
});
}
18 changes: 18 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 @@ -108,6 +108,14 @@ export interface AgentClient {
* Return the latest agent available version
*/
getLatestAgentAvailableVersion(includeCurrentVersion?: boolean): Promise<string>;
/**
* Return the latest agent available version, not taking into account IAR versions
*/
getLatestAgentAvailableBaseVersion(includeCurrentVersion?: boolean): Promise<string>;
/**
* Return the latest agent available version formatted for the docker image
*/
getLatestAgentAvailableDockerImageVersion(includeCurrentVersion?: boolean): Promise<string>;
}

/**
Expand Down Expand Up @@ -163,6 +171,16 @@ class AgentClientImpl implements AgentClient {
);
}

public async getLatestAgentAvailableBaseVersion(includeCurrentVersion?: boolean) {
const fullVersion = await this.getLatestAgentAvailableVersion(includeCurrentVersion);
return fullVersion.split('+')[0];
}

public async getLatestAgentAvailableDockerImageVersion(includeCurrentVersion?: boolean) {
const fullVersion = await this.getLatestAgentAvailableVersion(includeCurrentVersion);
return fullVersion.replace('+', '.');
}

public async getLatestAgentAvailableVersion(includeCurrentVersion?: boolean) {
await this.#runPreflight();
return getLatestAvailableAgentVersion({ includeCurrentVersion });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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.
*/

export interface ElasticAgentVersionInfo {
agentVersion: string;
agentBaseVersion: string;
agentDockerImageVersion: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function getAutoDetectCommand(
--kibana-url=${options.kibanaUrl}
--install-key=${options.installApiKey}
--ingest-key=${options.ingestApiKey}
--ea-version=${options.elasticAgentVersion}
--ea-version=${options.elasticAgentVersionInfo.agentVersion}
`;
}
function oneLine(parts: TemplateStringsArray, ...args: string[]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ export function InstallElasticAgent() {
apiKeyEncoded,
apiEndpoint: setup?.apiEndpoint,
scriptDownloadUrl: setup?.scriptDownloadUrl,
elasticAgentVersion: setup?.elasticAgentVersion,
elasticAgentVersion: setup?.elasticAgentVersionInfo.agentVersion,
autoDownloadConfig,
onboardingId,
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
* 2.0.
*/

import { ElasticAgentVersionInfo } from '../../../../common/types';

interface Params {
encodedApiKey: string;
onboardingId: string;
elasticsearchUrl: string;
elasticAgentVersion: string;
elasticAgentVersionInfo: ElasticAgentVersionInfo;
}

const KUSTOMIZE_TEMPLATE_URL =
Expand All @@ -19,16 +21,16 @@ export function buildKubectlCommand({
encodedApiKey,
onboardingId,
elasticsearchUrl,
elasticAgentVersion,
elasticAgentVersionInfo,
}: Params) {
const escapedElasticsearchUrl = elasticsearchUrl.replace(/\//g, '\\/');

return `
kubectl kustomize ${KUSTOMIZE_TEMPLATE_URL}\\?ref\\=v${elasticAgentVersion}
kubectl kustomize ${KUSTOMIZE_TEMPLATE_URL}\\?ref\\=v${elasticAgentVersionInfo.agentBaseVersion}
| sed -e 's/JUFQSV9LRVkl/${encodedApiKey}/g'
-e "s/%ES_HOST%/${escapedElasticsearchUrl}/g"
-e "s/%ONBOARDING_ID%/${onboardingId}/g"
-e "s/\\(docker.elastic.co\\/beats\\/elastic-agent\:\\).*$/\\1${elasticAgentVersion}/g"
-e "s/\\(docker.elastic.co\\/beats\\/elastic-agent\:\\).*$/\\1${elasticAgentVersionInfo.agentDockerImageVersion}/g"
-e "/{CA_TRUSTED}/c\\ "
| kubectl apply -f-
`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,30 @@ import React from 'react';
import { EuiCodeBlock, EuiLink, EuiSpacer, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { ElasticAgentVersionInfo } from '../../../../common/types';
import { buildKubectlCommand } from './build_kubectl_command';
import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button';

interface Props {
encodedApiKey: string;
onboardingId: string;
elasticsearchUrl: string;
elasticAgentVersion: string;
elasticAgentVersionInfo: ElasticAgentVersionInfo;
isCopyPrimaryAction: boolean;
}

export function CommandSnippet({
encodedApiKey,
onboardingId,
elasticsearchUrl,
elasticAgentVersion,
elasticAgentVersionInfo,
isCopyPrimaryAction,
}: Props) {
const command = buildKubectlCommand({
encodedApiKey,
onboardingId,
elasticsearchUrl,
elasticAgentVersion,
elasticAgentVersionInfo,
});

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const KubernetesPanel: React.FC = () => {
encodedApiKey={data.apiKeyEncoded}
onboardingId={data.onboardingId}
elasticsearchUrl={data.elasticsearchUrl}
elasticAgentVersion={data.elasticAgentVersion}
elasticAgentVersionInfo={data.elasticAgentVersionInfo}
isCopyPrimaryAction={!isMonitoringStepActive}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ export const OtelLogsPanel: React.FC = () => {
} = useKibana<ObservabilityOnboardingAppServices>();

const AGENT_CDN_BASE_URL = 'artifacts.elastic.co/downloads/beats/elastic-agent';
const agentVersion = isServerless ? setup?.elasticAgentVersion : stackVersion;
const agentVersion =
isServerless && setup ? setup.elasticAgentVersionInfo.agentVersion : stackVersion;
const urlEncodedAgentVersion = encodeURIComponent(agentVersion);

const allDatasetsLocator =
share.url.locators.get<AllDatasetsLocatorParams>(ALL_DATASETS_LOCATOR_ID);
Expand All @@ -96,7 +98,7 @@ export const OtelLogsPanel: React.FC = () => {
firstStepTitle: HOST_COMMAND,
content: `arch=$(if ([[ $(arch) == "arm" || $(arch) == "aarch64" ]]); then echo "arm64"; else echo $(arch); fi)

curl --output elastic-distro-${agentVersion}-linux-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${agentVersion}-linux-$arch.tar.gz --proto '=https' --tlsv1.2 -fOL && mkdir -p elastic-distro-${agentVersion}-linux-$arch && tar -xvf elastic-distro-${agentVersion}-linux-$arch.tar.gz -C "elastic-distro-${agentVersion}-linux-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-linux-$arch
curl --output elastic-distro-${agentVersion}-linux-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${urlEncodedAgentVersion}-linux-$arch.tar.gz --proto '=https' --tlsv1.2 -fOL && mkdir -p elastic-distro-${agentVersion}-linux-$arch && tar -xvf elastic-distro-${agentVersion}-linux-$arch.tar.gz -C "elastic-distro-${agentVersion}-linux-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-linux-$arch

rm ./otel.yml && cp ./otel_samples/platformlogs_hostmetrics.yml ./otel.yml && mkdir -p ./data/otelcol && sed -i 's#\\\${env:STORAGE_DIR}#'"$PWD"/data/otelcol'#g' ./otel.yml && sed -i 's#\\\${env:ELASTIC_ENDPOINT}#${setup?.elasticsearchUrl}#g' ./otel.yml && sed -i 's/\\\${env:ELASTIC_API_KEY}/${apiKeyData?.apiKeyEncoded}/g' ./otel.yml`,
start: 'sudo ./otelcol --config otel.yml',
Expand All @@ -108,7 +110,7 @@ rm ./otel.yml && cp ./otel_samples/platformlogs_hostmetrics.yml ./otel.yml && mk
firstStepTitle: HOST_COMMAND,
content: `arch=$(if [[ $(uname -m) == "arm64" ]]; then echo "aarch64"; else echo $(uname -m); fi)

curl --output elastic-distro-${agentVersion}-darwin-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${agentVersion}-darwin-$arch.tar.gz --proto '=https' --tlsv1.2 -fOL && mkdir -p "elastic-distro-${agentVersion}-darwin-$arch" && tar -xvf elastic-distro-${agentVersion}-darwin-$arch.tar.gz -C "elastic-distro-${agentVersion}-darwin-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-darwin-$arch
curl --output elastic-distro-${agentVersion}-darwin-$arch.tar.gz --url https://${AGENT_CDN_BASE_URL}/elastic-agent-${urlEncodedAgentVersion}-darwin-$arch.tar.gz --proto '=https' --tlsv1.2 -fOL && mkdir -p "elastic-distro-${agentVersion}-darwin-$arch" && tar -xvf elastic-distro-${agentVersion}-darwin-$arch.tar.gz -C "elastic-distro-${agentVersion}-darwin-$arch" --strip-components=1 && cd elastic-distro-${agentVersion}-darwin-$arch

rm ./otel.yml && cp ./otel_samples/platformlogs_hostmetrics.yml ./otel.yml && mkdir -p ./data/otelcol && sed -i '' 's#\\\${env:STORAGE_DIR}#'"$PWD"/data/otelcol'#g' ./otel.yml && sed -i '' 's#\\\${env:ELASTIC_ENDPOINT}#${setup?.elasticsearchUrl}#g' ./otel.yml && sed -i '' 's/\\\${env:ELASTIC_API_KEY}/${apiKeyData?.apiKeyEncoded}/g' ./otel.yml`,
start: './otelcol --config otel.yml',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,27 @@
*/

import type { FleetStartContract } from '@kbn/fleet-plugin/server';
import { ElasticAgentVersionInfo } from '../../common/types';

export function getAgentVersion(fleetStart: FleetStartContract, kibanaVersion: string) {
export async function getAgentVersionInfo(
fleetStart: FleetStartContract,
kibanaVersion: string
): Promise<ElasticAgentVersionInfo> {
// If undefined, we will follow fleet's strategy to select latest available version:
// for serverless we will use the latest published version, for statefull we will use
// current Kibana version. If false, irrespective of fleet flags and logic, we are
// explicitly deciding to not append the current version.
const includeCurrentVersion = kibanaVersion.endsWith('-SNAPSHOT') ? false : undefined;

const agentClient = fleetStart.agentService.asInternalUser;
return agentClient.getLatestAgentAvailableVersion(includeCurrentVersion);
const [agentVersion, agentBaseVersion, agentDockerImageVersion] = await Promise.all([
agentClient.getLatestAgentAvailableVersion(includeCurrentVersion),
agentClient.getLatestAgentAvailableBaseVersion(includeCurrentVersion),
agentClient.getLatestAgentAvailableDockerImageVersion(includeCurrentVersion),
]);
return {
agentVersion,
agentBaseVersion,
agentDockerImageVersion,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ObservabilityOnboardingFlow } from '../../saved_objects/observability_o
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getHasLogs } from './get_has_logs';
import { getKibanaUrl } from '../../lib/get_fallback_urls';
import { getAgentVersion } from '../../lib/get_agent_version';
import { getAgentVersionInfo } from '../../lib/get_agent_version';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { ElasticAgentStepPayload, InstalledIntegration, StepProgressPayloadRT } from '../types';
import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key';
Expand Down Expand Up @@ -220,21 +220,22 @@ const createFlowRoute = createObservabilityOnboardingServerRoute({

const fleetPluginStart = await plugins.fleet.start();

const [onboardingFlow, ingestApiKey, installApiKey, elasticAgentVersion] = await Promise.all([
saveObservabilityOnboardingFlow({
savedObjectsClient,
observabilityOnboardingState: {
type: 'autoDetect',
state: undefined,
progress: {},
},
}),
createShipperApiKey(client.asCurrentUser, `onboarding_ingest_${name}`),
(
await context.resolve(['core'])
).core.security.authc.apiKeys.create(createInstallApiKey(`onboarding_install_${name}`)),
getAgentVersion(fleetPluginStart, kibanaVersion),
]);
const [onboardingFlow, ingestApiKey, installApiKey, elasticAgentVersionInfo] =
await Promise.all([
saveObservabilityOnboardingFlow({
savedObjectsClient,
observabilityOnboardingState: {
type: 'autoDetect',
state: undefined,
progress: {},
},
}),
createShipperApiKey(client.asCurrentUser, `onboarding_ingest_${name}`),
(
await context.resolve(['core'])
).core.security.authc.apiKeys.create(createInstallApiKey(`onboarding_install_${name}`)),
getAgentVersionInfo(fleetPluginStart, kibanaVersion),
]);

if (!installApiKey) {
throw Boom.notFound('License does not allow API key creation.');
Expand All @@ -250,7 +251,7 @@ const createFlowRoute = createObservabilityOnboardingServerRoute({
onboardingFlow,
ingestApiKey: ingestApiKey.encoded,
installApiKey: installApiKey.encoded,
elasticAgentVersion,
elasticAgentVersionInfo,
kibanaUrl,
scriptDownloadUrl,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import * as t from 'io-ts';
import Boom from '@hapi/boom';
import { termQuery } from '@kbn/observability-plugin/server';
import type { estypes } from '@elastic/elasticsearch';
import { ElasticAgentVersionInfo } from '../../../common/types';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { hasLogMonitoringPrivileges } from '../../lib/api_key/has_log_monitoring_privileges';
import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key';
import { getAgentVersion } from '../../lib/get_agent_version';
import { getAgentVersionInfo } from '../../lib/get_agent_version';

export interface CreateKubernetesOnboardingFlowRouteResponse {
apiKeyEncoded: string;
onboardingId: string;
elasticsearchUrl: string;
elasticAgentVersion: string;
elasticAgentVersionInfo: ElasticAgentVersionInfo;
}

export interface HasKubernetesDataRouteResponse {
Expand Down Expand Up @@ -56,9 +57,9 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR
const fleetPluginStart = await plugins.fleet.start();
const packageClient = fleetPluginStart.packageService.asScoped(request);

const [{ encoded: apiKeyEncoded }, elasticAgentVersion] = await Promise.all([
const [{ encoded: apiKeyEncoded }, elasticAgentVersionInfo] = await Promise.all([
createShipperApiKey(client.asCurrentUser, `${params.body.pkgName}_onboarding`, true),
getAgentVersion(fleetPluginStart, kibanaVersion),
getAgentVersionInfo(fleetPluginStart, kibanaVersion),
// System package is always required
packageClient.ensureInstalledPackage({ pkgName: 'system' }),
// Kubernetes package is required for both classic kubernetes and otel
Expand All @@ -77,7 +78,7 @@ const createKubernetesOnboardingFlowRoute = createObservabilityOnboardingServerR
onboardingId: uuidv4(),
apiKeyEncoded,
elasticsearchUrl: elasticsearchUrlList.length > 0 ? elasticsearchUrlList[0] : '',
elasticAgentVersion,
elasticAgentVersionInfo,
};
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@

import * as t from 'io-ts';
import Boom from '@hapi/boom';
import { ElasticAgentVersionInfo } from '../../../common/types';
import { createObservabilityOnboardingServerRoute } from '../create_observability_onboarding_server_route';
import { getFallbackESUrl } from '../../lib/get_fallback_urls';
import { getKibanaUrl } from '../../lib/get_fallback_urls';
import { getAgentVersion } from '../../lib/get_agent_version';
import { getAgentVersionInfo } from '../../lib/get_agent_version';
import { saveObservabilityOnboardingFlow } from '../../lib/state';
import { createShipperApiKey } from '../../lib/api_key/create_shipper_api_key';
import { ObservabilityOnboardingFlow } from '../../saved_objects/observability_onboarding_status';
Expand Down Expand Up @@ -39,7 +40,7 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
async handler(resources): Promise<{
apiEndpoint: string;
scriptDownloadUrl: string;
elasticAgentVersion: string;
elasticAgentVersionInfo: ElasticAgentVersionInfo;
elasticsearchUrl: string[];
}> {
const {
Expand All @@ -50,7 +51,7 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
} = resources;

const fleetPluginStart = await plugins.fleet.start();
const elasticAgentVersion = await getAgentVersion(fleetPluginStart, kibanaVersion);
const elasticAgentVersionInfo = await getAgentVersionInfo(fleetPluginStart, kibanaVersion);
const kibanaUrl = getKibanaUrl(core.setup, plugins.cloud?.setup);
const scriptDownloadUrl = new URL(
core.setup.http.staticAssets.getPluginAssetHref('standalone_agent_setup.sh'),
Expand All @@ -67,7 +68,7 @@ const installShipperSetupRoute = createObservabilityOnboardingServerRoute({
apiEndpoint,
elasticsearchUrl,
scriptDownloadUrl,
elasticAgentVersion,
elasticAgentVersionInfo,
};
},
});
Expand Down
Loading