Skip to content

Commit

Permalink
In case of kubernetes integartion detected return manifest in standal…
Browse files Browse the repository at this point in the history
…one agent layout instead of policy (#114439)

* In case of kubernetes integartion detected return manifest in standalone agent layout instead of policy
  • Loading branch information
MichaelKatsoulis authored Oct 18, 2021
1 parent 3b822be commit fa183ff
Show file tree
Hide file tree
Showing 11 changed files with 568 additions and 80 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/common/constants/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export const FLEET_SERVER_PACKAGE = 'fleet_server';
export const FLEET_ENDPOINT_PACKAGE = 'endpoint';
export const FLEET_APM_PACKAGE = 'apm';
export const FLEET_SYNTHETICS_PACKAGE = 'synthetics';
export const FLEET_KUBERNETES_PACKAGE = 'kubernetes';
export const KUBERNETES_RUN_INSTRUCTIONS =
'kubectl apply -f elastic-agent-standalone-kubernetes.yaml';
export const STANDALONE_RUN_INSTRUCTIONS = './elastic-agent install';

/*
Package rules:
Expand Down
34 changes: 34 additions & 0 deletions x-pack/plugins/fleet/common/services/agent_cm_to_yaml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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 type { safeDump } from 'js-yaml';

import type { FullAgentConfigMap } from '../types/models/agent_cm';

const CM_KEYS_ORDER = ['apiVersion', 'kind', 'metadata', 'data'];

export const fullAgentConfigMapToYaml = (
policy: FullAgentConfigMap,
toYaml: typeof safeDump
): string => {
return toYaml(policy, {
skipInvalid: true,
sortKeys: (keyA: string, keyB: string) => {
const indexA = CM_KEYS_ORDER.indexOf(keyA);
const indexB = CM_KEYS_ORDER.indexOf(keyB);
if (indexA >= 0 && indexB < 0) {
return -1;
}

if (indexA < 0 && indexB >= 0) {
return 1;
}

return indexA - indexB;
},
});
};
29 changes: 29 additions & 0 deletions x-pack/plugins/fleet/common/types/models/agent_cm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 type { FullAgentPolicy } from './agent_policy';

export interface FullAgentConfigMap {
apiVersion: string;
kind: string;
metadata: Metadata;
data: AgentYML;
}

interface Metadata {
name: string;
namespace: string;
labels: Labels;
}

interface Labels {
'k8s-app': string;
}

interface AgentYML {
'agent.yml': FullAgentPolicy;
}
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/common/types/rest_spec/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,7 @@ export interface GetFullAgentPolicyRequest {
export interface GetFullAgentPolicyResponse {
item: FullAgentPolicy;
}

export interface GetFullAgentConfigMapResponse {
item: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useEffect } from 'react';
import {
EuiSteps,
EuiText,
Expand All @@ -23,56 +23,168 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { safeDump } from 'js-yaml';

import { useStartServices, useLink, sendGetOneAgentPolicyFull } from '../../hooks';
import {
useStartServices,
useLink,
sendGetOneAgentPolicyFull,
sendGetOneAgentPolicy,
} from '../../hooks';
import { fullAgentPolicyToYaml, agentPolicyRouteService } from '../../services';

import type { PackagePolicy } from '../../../common';

import {
FLEET_KUBERNETES_PACKAGE,
KUBERNETES_RUN_INSTRUCTIONS,
STANDALONE_RUN_INSTRUCTIONS,
} from '../../../common';

import { DownloadStep, AgentPolicySelectionStep } from './steps';
import type { BaseProps } from './types';

type Props = BaseProps;

const RUN_INSTRUCTIONS = './elastic-agent install';

export const StandaloneInstructions = React.memo<Props>(({ agentPolicy, agentPolicies }) => {
const { getHref } = useLink();
const core = useStartServices();
const { notifications } = core;

const [selectedPolicyId, setSelectedPolicyId] = useState<string | undefined>(agentPolicy?.id);
const [fullAgentPolicy, setFullAgentPolicy] = useState<any | undefined>();
const [isK8s, setIsK8s] = useState<'IS_LOADING' | 'IS_KUBERNETES' | 'IS_NOT_KUBERNETES'>(
'IS_LOADING'
);
const [yaml, setYaml] = useState<string | string>('');
const runInstructions =
isK8s === 'IS_KUBERNETES' ? KUBERNETES_RUN_INSTRUCTIONS : STANDALONE_RUN_INSTRUCTIONS;

const downloadLink = selectedPolicyId
? core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(selectedPolicyId)}?standalone=true`
)
: undefined;
useEffect(() => {
async function checkifK8s() {
if (!selectedPolicyId) {
return;
}
const agentPolicyRequest = await sendGetOneAgentPolicy(selectedPolicyId);
const agentPol = agentPolicyRequest.data ? agentPolicyRequest.data.item : null;

if (!agentPol) {
setIsK8s('IS_NOT_KUBERNETES');
return;
}
const k8s = (pkg: PackagePolicy) => pkg.package?.name === FLEET_KUBERNETES_PACKAGE;
setIsK8s(
(agentPol.package_policies as PackagePolicy[]).some(k8s)
? 'IS_KUBERNETES'
: 'IS_NOT_KUBERNETES'
);
}
checkifK8s();
}, [selectedPolicyId, notifications.toasts]);

useEffect(() => {
async function fetchFullPolicy() {
try {
if (!selectedPolicyId) {
return;
}
const res = await sendGetOneAgentPolicyFull(selectedPolicyId, { standalone: true });
let query = { standalone: true, kubernetes: false };
if (isK8s === 'IS_KUBERNETES') {
query = { standalone: true, kubernetes: true };
}
const res = await sendGetOneAgentPolicyFull(selectedPolicyId, query);
if (res.error) {
throw res.error;
}

if (!res.data) {
throw new Error('No data while fetching full agent policy');
}

setFullAgentPolicy(res.data.item);
} catch (error) {
notifications.toasts.addError(error, {
title: 'Error',
});
}
}
fetchFullPolicy();
}, [selectedPolicyId, notifications.toasts]);
if (isK8s !== 'IS_LOADING') {
fetchFullPolicy();
}
}, [selectedPolicyId, notifications.toasts, isK8s, core.http.basePath]);

useEffect(() => {
if (isK8s === 'IS_KUBERNETES') {
if (typeof fullAgentPolicy === 'object') {
return;
}
setYaml(fullAgentPolicy);
} else {
if (typeof fullAgentPolicy === 'string') {
return;
}
setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump));
}
}, [fullAgentPolicy, isK8s]);

const policyMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescriptionk8s"
defaultMessage="Copy or download the Kubernetes manifest inside the Kubernetes cluster. Modify {ESUsernameVariable} and {ESPasswordVariable} in the Daemonset environment variables and apply the manifest."
values={{
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
}}
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescription"
defaultMessage="Copy this policy to the {fileName} on the host where the Elastic Agent is installed. Modify {ESUsernameVariable} and {ESPasswordVariable} in the {outputSection} section of {fileName} to use your Elasticsearch credentials."
values={{
fileName: <EuiCode>elastic-agent.yml</EuiCode>,
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
outputSection: <EuiCode>outputs</EuiCode>,
}}
/>
);

let downloadLink = '';
if (selectedPolicyId) {
downloadLink =
isK8s === 'IS_KUBERNETES'
? core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(selectedPolicyId)}?kubernetes=true`
)
: core.http.basePath.prepend(
`${agentPolicyRouteService.getInfoFullDownloadPath(selectedPolicyId)}?standalone=true`
);
}

const downloadMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButtonk8s"
defaultMessage="Download Manifest"
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButton"
defaultMessage="Download Policy"
/>
);

const applyMsg =
isK8s === 'IS_KUBERNETES' ? (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepRunAgentDescriptionk8s"
defaultMessage="From the directory where the Kubernetes manifest is downloaded, run the apply command."
/>
) : (
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepRunAgentDescription"
defaultMessage="From the agent directory, run this command to install, enroll and start an Elastic Agent. You can reuse this command to set up agents on more than one host. Requires administrator privileges."
/>
);

const yaml = useMemo(() => fullAgentPolicyToYaml(fullAgentPolicy, safeDump), [fullAgentPolicy]);
const steps = [
DownloadStep(),
!agentPolicy
Expand All @@ -85,16 +197,7 @@ export const StandaloneInstructions = React.memo<Props>(({ agentPolicy, agentPol
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescription"
defaultMessage="Copy this policy to the {fileName} on the host where the Elastic Agent is installed. Modify {ESUsernameVariable} and {ESPasswordVariable} in the {outputSection} section of {fileName} to use your Elasticsearch credentials."
values={{
fileName: <EuiCode>elastic-agent.yml</EuiCode>,
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>,
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>,
outputSection: <EuiCode>outputs</EuiCode>,
}}
/>
<>{policyMsg}</>
<EuiSpacer size="m" />
<EuiFlexGroup gutterSize="m">
<EuiFlexItem grow={false}>
Expand All @@ -111,10 +214,7 @@ export const StandaloneInstructions = React.memo<Props>(({ agentPolicy, agentPol
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton iconType="download" href={downloadLink} isDisabled={!downloadLink}>
<FormattedMessage
id="xpack.fleet.agentEnrollment.downloadPolicyButton"
defaultMessage="Download policy"
/>
<>{downloadMsg}</>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
Expand All @@ -133,14 +233,11 @@ export const StandaloneInstructions = React.memo<Props>(({ agentPolicy, agentPol
children: (
<>
<EuiText>
<FormattedMessage
id="xpack.fleet.agentEnrollment.stepRunAgentDescription"
defaultMessage="From the agent directory, run this command to install, enroll and start an Elastic Agent. You can reuse this command to set up agents on more than one host. Requires administrator privileges."
/>
<>{applyMsg}</>
<EuiSpacer size="m" />
<EuiCodeBlock fontSize="m">{RUN_INSTRUCTIONS}</EuiCodeBlock>
<EuiCodeBlock fontSize="m">{runInstructions}</EuiCodeBlock>
<EuiSpacer size="m" />
<EuiCopy textToCopy={RUN_INSTRUCTIONS}>
<EuiCopy textToCopy={runInstructions}>
{(copy) => (
<EuiButton onClick={copy} iconType="copyClipboard">
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const useGetOneAgentPolicyFull = (agentPolicyId: string) => {

export const sendGetOneAgentPolicyFull = (
agentPolicyId: string,
query: { standalone?: boolean } = {}
query: { standalone?: boolean; kubernetes?: boolean } = {}
) => {
return sendRequest<GetFullAgentPolicyResponse>({
path: agentPolicyRouteService.getInfoFullPath(agentPolicyId),
Expand Down
Loading

0 comments on commit fa183ff

Please sign in to comment.