-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
In case of kubernetes integration detected return manifest in standalone agent layout instead of policy #114439
Changes from 6 commits
be7ffd3
158fce6
6793345
923d19f
d2a5d84
84060cd
7258881
29a6b0f
340c04c
0fdce64
2cb0b6b
b4f55ad
ef47868
1f7a89e
c4c107b
3004261
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
}, | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/* | ||
* 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: FullAgentPolicy; | ||
} | ||
|
||
interface Metadata { | ||
name: string; | ||
namespace: string; | ||
labels: Labels; | ||
} | ||
|
||
interface Labels { | ||
'k8s-app': string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,56 +23,184 @@ 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 { DownloadStep, AgentPolicySelectionStep } from './steps'; | ||
import type { BaseProps } from './types'; | ||
|
||
type Props = BaseProps; | ||
|
||
const RUN_INSTRUCTIONS = './elastic-agent install'; | ||
|
||
export const elasticAgentPolicy = ''; | ||
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<string | undefined>('isLoading'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. using true and false as string is a little error prone what do you thing of using this types |
||
const [yaml, setYaml] = useState<string | string>(''); | ||
const [runInstructions, setRunInstructions] = useState<string | string>(''); | ||
const [downloadLink, setDownloadLink] = useState<string | undefined>(); | ||
|
||
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('false'); | ||
return; | ||
} | ||
let found = false; | ||
(agentPol.package_policies as PackagePolicy[]).forEach(({ package: pkg }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this whole block could probably be simplified using |
||
if (!pkg) { | ||
return; | ||
} | ||
if (pkg.name === 'kubernetes') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could define a constant for that package https://github.com/elastic/kibana/blob/master/x-pack/plugins/fleet/common/constants/epm.ts/#L15 |
||
found = true; | ||
return; | ||
} | ||
}); | ||
|
||
if (found) { | ||
setIsK8s('true'); | ||
} else { | ||
setIsK8s('false'); | ||
} | ||
} | ||
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 }; | ||
let downloandLinkUrl = `${agentPolicyRouteService.getInfoFullDownloadPath( | ||
selectedPolicyId | ||
)}?standalone=true`; | ||
if (isK8s === 'true') { | ||
query = { standalone: true, kubernetes: true }; | ||
downloandLinkUrl = `${agentPolicyRouteService.getInfoFullDownloadPath( | ||
selectedPolicyId | ||
)}?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); | ||
setDownloadLink(core.http.basePath.prepend(downloandLinkUrl)); | ||
} catch (error) { | ||
notifications.toasts.addError(error, { | ||
title: 'Error', | ||
}); | ||
} | ||
} | ||
fetchFullPolicy(); | ||
}, [selectedPolicyId, notifications.toasts]); | ||
if (isK8s !== 'isLoading') { | ||
fetchFullPolicy(); | ||
} | ||
}, [selectedPolicyId, notifications.toasts, isK8s, core.http.basePath]); | ||
|
||
useMemo(() => { | ||
if (isK8s === 'true') { | ||
if (typeof fullAgentPolicy === 'object') { | ||
return; | ||
} | ||
setYaml(fullAgentPolicy); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You probably do not need the So you could do
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is very tricky. The reason I did it like this is that I didn't want always this usememo to return a value for the yaml. There are unfortunately some race conditions where isk8s === true but fullAgentPolicy hasn't beed updated yet(remains an object instead of string) and also the opposite. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case you should probably use |
||
setRunInstructions('kubectl apply -f elastic-agent.yml'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick but maybe move this two cmd in a constant in the top of the file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you probably do not need a state
|
||
} else { | ||
if (typeof fullAgentPolicy === 'string') { | ||
return; | ||
} | ||
setYaml(fullAgentPolicyToYaml(fullAgentPolicy, safeDump)); | ||
setRunInstructions('./elastic-agent install'); | ||
} | ||
}, [fullAgentPolicy, isK8s]); | ||
|
||
function policyMsg() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to be consistent with how we do that in other part of fleet and avoid creating a function you could do this like
|
||
if (isK8s === 'true') { | ||
return ( | ||
<FormattedMessage | ||
id="xpack.fleet.agentEnrollment.stepConfigureAgentDescriptionk8s" | ||
defaultMessage="Copy or download the Kubernetes manifest inside the Kubernetes cluster. Modify {ESUsernameVariable}, {ESPasswordVariable} and {ESHostVariable} in the Daemonset environment variables and apply the manifest." | ||
values={{ | ||
ESUsernameVariable: <EuiCode>ES_USERNAME</EuiCode>, | ||
ESPasswordVariable: <EuiCode>ES_PASSWORD</EuiCode>, | ||
ESHostVariable: <EuiCode>ES_HOST</EuiCode>, | ||
}} | ||
/> | ||
); | ||
} else { | ||
return ( | ||
<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>, | ||
}} | ||
/> | ||
); | ||
} | ||
} | ||
|
||
function downloadMsg() { | ||
if (isK8s === 'true') { | ||
return ( | ||
<FormattedMessage | ||
id="xpack.fleet.agentEnrollment.downloadPolicyButtonk8s" | ||
defaultMessage="Download Manifest" | ||
/> | ||
); | ||
} else { | ||
return ( | ||
<FormattedMessage | ||
id="xpack.fleet.agentEnrollment.downloadPolicyButton" | ||
defaultMessage="Download Policy" | ||
/> | ||
); | ||
} | ||
} | ||
|
||
function applyMsg() { | ||
if (isK8s === 'true') { | ||
return ( | ||
<FormattedMessage | ||
id="xpack.fleet.agentEnrollment.stepRunAgentDescriptionk8s" | ||
defaultMessage="From the directory where the Kubernetes manifest is downloaded, run the apply command." | ||
/> | ||
); | ||
} else { | ||
return ( | ||
<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 | ||
|
@@ -85,16 +213,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}> | ||
|
@@ -111,10 +230,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> | ||
|
@@ -133,14 +249,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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems unused?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are right