Skip to content

Commit

Permalink
[8.x] [UII] Fix first time integration flow when using multiple spaces (
Browse files Browse the repository at this point in the history
#194350) (#194882)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[UII] Fix first time integration flow when using multiple spaces
(#194350)](#194350)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Jen
Huang","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-03T23:07:04Z","message":"[UII]
Fix first time integration flow when using multiple spaces
(#194350)\n\n## Summary\r\n\r\nResolves #182736. This PR fixes the first
time Elastic Agent user\r\nonboarding flow (only shown on Cloud) by
prefixing the user's current\r\nspace ID to the default agent policy
that is created behind the scenes\r\nif the user has opted into using
Fleet's space awareness.\r\n\r\nThis makes it so that Fleet correctly
identifies whether there are any\r\nagents enrolled in the current
space, and directs the user to the\r\nmulti-step onboarding flow for
adding integrations if there are no\r\nagents enrolled in the current
space's scoped agent policies.\r\n\r\nAn example policy ID created this
way may look like\r\n`second-space:fleet-first-agent-policy`. If the
current space is the\r\n`default` space, the ID remains as
`fleet-first-agent-policy`.\r\n\r\nDue to prefixing using a `:`, I also
had to put policy ID in quotes\r\nwhere ever it appears in kueries for
filtering, i.e. `policy_id:\"<some\r\nid>\"`. This change comprises most
of the changed files in this
PR.","sha":"6f213cfb1cf548785271b89dd8199e13d09fd578","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Fleet","v9.0.0","backport:prev-minor","ci:cloud-deploy"],"title":"[UII]
Fix first time integration flow when using multiple
spaces","number":194350,"url":"https://github.com/elastic/kibana/pull/194350","mergeCommit":{"message":"[UII]
Fix first time integration flow when using multiple spaces
(#194350)\n\n## Summary\r\n\r\nResolves #182736. This PR fixes the first
time Elastic Agent user\r\nonboarding flow (only shown on Cloud) by
prefixing the user's current\r\nspace ID to the default agent policy
that is created behind the scenes\r\nif the user has opted into using
Fleet's space awareness.\r\n\r\nThis makes it so that Fleet correctly
identifies whether there are any\r\nagents enrolled in the current
space, and directs the user to the\r\nmulti-step onboarding flow for
adding integrations if there are no\r\nagents enrolled in the current
space's scoped agent policies.\r\n\r\nAn example policy ID created this
way may look like\r\n`second-space:fleet-first-agent-policy`. If the
current space is the\r\n`default` space, the ID remains as
`fleet-first-agent-policy`.\r\n\r\nDue to prefixing using a `:`, I also
had to put policy ID in quotes\r\nwhere ever it appears in kueries for
filtering, i.e. `policy_id:\"<some\r\nid>\"`. This change comprises most
of the changed files in this
PR.","sha":"6f213cfb1cf548785271b89dd8199e13d09fd578"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194350","number":194350,"mergeCommit":{"message":"[UII]
Fix first time integration flow when using multiple spaces
(#194350)\n\n## Summary\r\n\r\nResolves #182736. This PR fixes the first
time Elastic Agent user\r\nonboarding flow (only shown on Cloud) by
prefixing the user's current\r\nspace ID to the default agent policy
that is created behind the scenes\r\nif the user has opted into using
Fleet's space awareness.\r\n\r\nThis makes it so that Fleet correctly
identifies whether there are any\r\nagents enrolled in the current
space, and directs the user to the\r\nmulti-step onboarding flow for
adding integrations if there are no\r\nagents enrolled in the current
space's scoped agent policies.\r\n\r\nAn example policy ID created this
way may look like\r\n`second-space:fleet-first-agent-policy`. If the
current space is the\r\n`default` space, the ID remains as
`fleet-first-agent-policy`.\r\n\r\nDue to prefixing using a `:`, I also
had to put policy ID in quotes\r\nwhere ever it appears in kueries for
filtering, i.e. `policy_id:\"<some\r\nid>\"`. This change comprises most
of the changed files in this
PR.","sha":"6f213cfb1cf548785271b89dd8199e13d09fd578"}}]}] BACKPORT-->

Co-authored-by: Jen Huang <[email protected]>
  • Loading branch information
kibanamachine and jen-huang authored Oct 4, 2024
1 parent edde4bd commit ece07e0
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ export const AgentPolicyActionMenu = memo<{
{isUpgradeAgentsModalOpen && (
<EuiPortal>
<AgentUpgradeAgentModal
agents={`policy_id: ${agentPolicy.id}`}
agents={`policy_id:"${agentPolicy.id}"`}
agentCount={agentPolicy.agents || 0}
onClose={() => {
setIsUpgradeAgentsModalOpen(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { useEffect, useState } from 'react';
import { useEffect, useState, useMemo } from 'react';
import { i18n } from '@kbn/i18n';

import { generateNewAgentPolicyWithDefaults } from '../../../../../../../../common/services/generate_new_agent_policy';
Expand All @@ -14,26 +14,13 @@ import {
sendCreateAgentPolicy,
sendGetOneAgentPolicy,
sendGetEnrollmentAPIKeys,
useFleetStatus,
} from '../../../../../../../hooks';

import type { AgentPolicy, NewAgentPolicy, EnrollmentAPIKey } from '../../../../../../../types';
import type { AgentPolicy, EnrollmentAPIKey } from '../../../../../../../types';

interface UseGetAgentPolicyOrDefaultResponse {
isLoading: boolean;
error?: Error;
agentPolicy?: AgentPolicy;
enrollmentAPIKey?: EnrollmentAPIKey;
created?: boolean;
}
export const DEFAULT_AGENT_POLICY_ID: string = 'fleet-first-agent-policy';
export const DEFAULT_AGENT_POLICY: NewAgentPolicy = Object.freeze(
generateNewAgentPolicyWithDefaults({
id: DEFAULT_AGENT_POLICY_ID,
name: i18n.translate('xpack.fleet.createPackagePolicy.firstAgentPolicyNameText', {
defaultMessage: 'My first agent policy',
}),
})
);
// Manual default space ID because importing from `@kbn/core-saved-objects-utils-server` is not allowed here
const DEFAULT_NAMESPACE_STRING = 'default';

const sendGetAgentPolicy = async (agentPolicyId: string) => {
let result;
Expand All @@ -54,71 +41,107 @@ const sendGetAgentPolicy = async (agentPolicyId: string) => {
return { data: result?.data };
};

const sendCreateDefaultAgentPolicy = sendCreateAgentPolicy.bind(null, DEFAULT_AGENT_POLICY);

export function useGetAgentPolicyOrDefault(agentPolicyIdIn?: string) {
const [result, setResult] = useState<UseGetAgentPolicyOrDefaultResponse>({ isLoading: true });
const { spaceId, isSpaceAwarenessEnabled } = useFleetStatus();
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error>();
const [agentPolicyResponse, setAgentPolicyResponse] = useState<AgentPolicy>();
const [enrollmentAPIKey, setEnrollmentAPIKey] = useState<EnrollmentAPIKey>();
const [created, setCreated] = useState<boolean>();

// If space awareness is enabled, append current space ID to the agent policy ID
// If current space is the default space, do not append the space ID for BWC
const defaultFirstPolicyIdBase = 'fleet-first-agent-policy';
const defaultFirstPolicyId = useMemo(
() =>
isSpaceAwarenessEnabled && spaceId !== DEFAULT_NAMESPACE_STRING
? `${spaceId}:${defaultFirstPolicyIdBase}`
: defaultFirstPolicyIdBase,
[isSpaceAwarenessEnabled, spaceId]
);

const defaultFirstPolicy = useMemo(
() =>
Object.freeze(
generateNewAgentPolicyWithDefaults({
id: defaultFirstPolicyId,
name: i18n.translate('xpack.fleet.createPackagePolicy.firstAgentPolicyNameText', {
defaultMessage: 'My first agent policy',
}),
})
),
[defaultFirstPolicyId]
);

useEffect(() => {
const getAgentPolicyOrDefault = async () => {
const agentPolicyId = agentPolicyIdIn || DEFAULT_AGENT_POLICY_ID;
const { data: agentPolicyData, error: getError } = await sendGetAgentPolicy(agentPolicyId);
setIsLoading(true);

const agentPolicyId = agentPolicyIdIn || defaultFirstPolicyId;
const { data: agentPolicyData, error: getError } = await sendGetAgentPolicy(agentPolicyId);
const existingAgentPolicy = agentPolicyData?.item;

if (agentPolicyIdIn && !existingAgentPolicy) {
setResult({
isLoading: false,
error: new Error(`Agent policy ${agentPolicyId} not found`),
});
setIsLoading(false);
setError(new Error(`Agent policy ${agentPolicyId} not found`));
return;
}

let createdAgentPolicy;

if (getError) {
setResult({ isLoading: false, error: getError });
setIsLoading(false);
setError(getError);
return;
}

if (!existingAgentPolicy) {
const { data: createdAgentPolicyData, error: createError } =
await sendCreateDefaultAgentPolicy();
await sendCreateAgentPolicy.bind(null, defaultFirstPolicy)();

if (createError) {
setResult({ isLoading: false, error: createError });
setIsLoading(false);
setError(createError);
return;
}

createdAgentPolicy = createdAgentPolicyData!.item;
setCreated(true);
}

const agentPolicy = (existingAgentPolicy || createdAgentPolicy) as AgentPolicy;
setAgentPolicyResponse(agentPolicy);

const { data: apiKeysData, error: apiKeysError } = await sendGetEnrollmentAPIKeys({
page: 1,
perPage: 1,
kuery: `policy_id:${agentPolicyId}`,
kuery: `policy_id:"${agentPolicyId}"`,
});

if (apiKeysError) {
setResult({ isLoading: false, error: apiKeysError });
setIsLoading(false);
setError(apiKeysError);
return;
}

if (!apiKeysData || !apiKeysData.items?.length) {
setResult({
isLoading: false,
error: new Error(`No enrollment API key found for policy ${agentPolicyId}`),
});
setIsLoading(false);
setError(new Error(`No enrollment API key found for policy ${agentPolicyId}`));
return;
}

const enrollmentAPIKey = apiKeysData.items[0];

setResult({ isLoading: false, created: !!createdAgentPolicy, agentPolicy, enrollmentAPIKey });
setIsLoading(false);
setEnrollmentAPIKey(apiKeysData.items[0]);
};

getAgentPolicyOrDefault();
}, [agentPolicyIdIn]);

return result;
}, [agentPolicyIdIn, defaultFirstPolicy, defaultFirstPolicyId]);

return {
isLoading,
error,
agentPolicy: agentPolicyResponse,
enrollmentAPIKey,
created,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const PostInstallAzureArmTemplateModal: React.FunctionComponent<{
sendGetEnrollmentAPIKeys({
page: 1,
perPage: 1,
kuery: `policy_id:${agentPolicy.id}`,
kuery: `policy_id:"${agentPolicy.id}"`,
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const PostInstallCloudFormationModal: React.FunctionComponent<{
sendGetEnrollmentAPIKeys({
page: 1,
perPage: 1,
kuery: `policy_id:${agentPolicy.id}`,
kuery: `policy_id:"${agentPolicy.id}"`,
})
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const PostInstallGoogleCloudShellModal: React.FunctionComponent<{
sendGetEnrollmentAPIKeys({
page: 1,
perPage: 1,
kuery: `policy_id:${agentPolicy.id}`,
kuery: `policy_id:"${agentPolicy.id}"`,
})
);
const { fleetServerHost, fleetProxy, downloadSource } = useFleetServerHostsForPolicy(agentPolicy);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const useIsFirstTimeAgentUserQuery = (): UseIsFirstTimeAgentUserResponse
const policyIds = [...new Set(packagePolicies?.items.flatMap((item) => item.policy_ids) ?? [])];

// now get all agents that are NOT part of a fleet server policy
const serverPolicyIdsQuery = policyIds.map((policyId) => `policy_id:${policyId}`).join(' or ');
const serverPolicyIdsQuery = policyIds.map((policyId) => `policy_id:"${policyId}"`).join(' or ');

// get agents that are not unenrolled and not fleet server
const kuery =
Expand Down
4 changes: 2 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 @@ -70,15 +70,15 @@ export async function populateAssignedAgentsCount(
showInactive: true,
perPage: 0,
page: 1,
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`,
kuery: `${AGENTS_PREFIX}.policy_id:"${agentPolicy.id}"`,
})
.then(({ total }) => (agentPolicy.agents = total));
const unprivilegedAgents = agentClient
.listAgents({
showInactive: true,
perPage: 0,
page: 1,
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id} and ${UNPRIVILEGED_AGENT_KUERY}`,
kuery: `${AGENTS_PREFIX}.policy_id:"${agentPolicy.id}" and ${UNPRIVILEGED_AGENT_KUERY}`,
})
.then(({ total }) => (agentPolicy.unprivileged_agents = total));
return Promise.all([totalAgents, unprivilegedAgents]);
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ class AgentPolicyService {
showInactive: true,
perPage: 0,
page: 1,
kuery: `${AGENTS_PREFIX}.policy_id:${agentPolicy.id}`,
kuery: `${AGENTS_PREFIX}.policy_id:"${agentPolicy.id}"`,
}).then(({ total }) => (agentPolicy.agents = total));
} else {
agentPolicy.agents = 0;
Expand Down Expand Up @@ -1161,7 +1161,7 @@ class AgentPolicyService {
showInactive: true,
perPage: 0,
page: 1,
kuery: `${AGENTS_PREFIX}.policy_id:${id}`,
kuery: `${AGENTS_PREFIX}.policy_id:"${id}"`,
});

if (total > 0 && !agentPolicy?.supports_agentless) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export async function deleteEnrollmentApiKeyForAgentPolicyId(
const { items } = await listEnrollmentApiKeys(esClient, {
page: page++,
perPage: 100,
kuery: `policy_id:${agentPolicyId}`,
kuery: `policy_id:"${agentPolicyId}"`,
});

if (items.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/server/services/fleet_server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const hasFleetServersForPolicies = async (
? `namespaces:"${spaceIds?.[0]}"`
: `not namespaces:* or namespaces:"${DEFAULT_SPACE_ID}"`;

return `(policy_id:${id} and (${space}))`;
return `(policy_id:"${id}" and (${space}))`;
})
.join(' or ')
);
Expand Down

0 comments on commit ece07e0

Please sign in to comment.