[Fleet] Change 'Out-of-date' to 'Outdated policy' in agent list table (#164673)

## Summary

Closes #164575.

Bonus: also fix the bug where the warning goes under the policy revision
I thought that looked a bit weird.

Double bonus: Updated the create agent test script to be able to create
outdated agents + to use nicer policy names so the table doesnt get
messy

Before:

<img width="1237" alt="Screenshot 2023-08-24 at 10 10 58"
src="86a88ad6-a2c7-4f17-82bb-fe026d05d937">


After:

<img width="1244" alt="Screenshot 2023-08-24 at 10 09 53"
src="1cf76dd3-2e6a-4aa9-a004-109797f54c52">
This commit is contained in:
Mark Hopkin 2023-08-24 12:05:44 +01:00 committed by GitHub
parent 86224d4cf6
commit 1610e32972
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 12 deletions

View file

@ -180,9 +180,11 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
const showWarning = agent.policy_revision && agentPolicy?.revision > agent.policy_revision;
return (
<EuiFlexGroup gutterSize="none" style={{ minWidth: 0 }} direction="column">
<EuiFlexGroup gutterSize="m" style={{ minWidth: 0 }} alignItems="center">
{agentPolicy && (
<AgentPolicySummaryLine direction="column" policy={agentPolicy} agent={agent} />
<EuiFlexItem grow={false}>
<AgentPolicySummaryLine direction="column" policy={agentPolicy} agent={agent} />
</EuiFlexItem>
)}
{showWarning && (
<EuiFlexItem grow={false}>
@ -191,7 +193,7 @@ export const AgentListTable: React.FC<Props> = (props: Props) => {
&nbsp;
<FormattedMessage
id="xpack.fleet.agentList.outOfDateLabel"
defaultMessage="Out-of-date"
defaultMessage="Outdated policy"
/>
</EuiText>
</EuiFlexItem>

View file

@ -8,6 +8,7 @@ import fetch from 'node-fetch';
import { ToolingLog } from '@kbn/tooling-log';
import { v4 as uuidv4 } from 'uuid';
import yargs from 'yargs';
import { omit } from 'lodash';
import type { AgentStatus } from '../../common';
import type { Agent } from '../../common';
@ -27,6 +28,7 @@ const printUsage = () =>
[--password]: password for kibana, defaults to changeme
[--batches]: run the script in batches, defaults to 1 e.g if count is 50 and batches is 10, 500 agents will be created and 10 agent policies
[--concurrentBatches]: how many batches to run concurrently, defaults to 10
[--outdated]: agents will show as outdated (their revision is below the policies), defaults to false
`);
const DEFAULT_KIBANA_URL = 'http://localhost:5601';
@ -51,7 +53,8 @@ const {
agentVersion: agentVersionArg,
username: kbnUsername = DEFAULT_KIBANA_USERNAME,
password: kbnPassword = DEFAULT_KIBANA_PASSWORD,
batches: batchesArg,
batches: batchesArg = 1,
outdated: outdatedArg = false,
concurrentBatches: concurrentBatchesArg = 10,
// ignore yargs positional args, we only care about named args
_,
@ -133,10 +136,12 @@ function createAgentWithStatus({
policyId,
status,
version,
hostname,
}: {
policyId: string;
status: AgentStatus;
version: string;
hostname: string;
}) {
const baseAgent = {
access_api_key_id: 'api-key-1',
@ -153,7 +158,7 @@ function createAgentWithStatus({
version,
},
},
host: { hostname: uuidv4() },
host: { hostname },
},
user_provided_metadata: {},
enrolled_at: new Date().toISOString(),
@ -167,7 +172,8 @@ function createAgentWithStatus({
function createAgentsWithStatuses(
statusMap: Partial<{ [status in AgentStatus]: number }>,
policyId: string,
version: string
version: string,
namePrefix?: string
) {
// loop over statuses and create agents with that status
const agents = [];
@ -176,13 +182,35 @@ function createAgentsWithStatuses(
const currentAgentStatus = currentStatus as AgentStatus;
const statusCount = statusMap[currentAgentStatus] || 0;
for (let i = 0; i < statusCount; i++) {
agents.push(createAgentWithStatus({ policyId, status: currentAgentStatus, version }));
const hostname = `${namePrefix ? namePrefix + '-' : ''}${currentAgentStatus}-${i}`;
agents.push(
createAgentWithStatus({ policyId, status: currentAgentStatus, version, hostname })
);
}
}
return agents;
}
async function getAgentPolicy(id: string) {
const res = await fetch(`${kibanaUrl}/api/fleet/agent_policies/${id}`, {
method: 'get',
headers: {
Authorization: kbnAuth,
'Content-Type': 'application/json',
'kbn-xsrf': 'kibana',
'x-elastic-product-origin': 'fleet',
},
});
const data = await res.json();
if (!data.item) {
logger.error('Agent policy not found, API response: ' + JSON.stringify(data));
process.exit(1);
}
return data;
}
async function deleteAgents() {
const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64');
const res = await fetch(`${ES_URL}/.fleet-agents/_delete_by_query`, {
@ -256,12 +284,12 @@ async function createSuperUser() {
return { role, user };
}
async function createAgentPolicy(id: string) {
async function createAgentPolicy(id: string, name: string) {
const res = await fetch(`${kibanaUrl}/api/fleet/agent_policies`, {
method: 'post',
body: JSON.stringify({
id,
name: id,
name,
namespace: 'default',
description: '',
monitoring_enabled: ['logs', 'metrics'],
@ -277,12 +305,56 @@ async function createAgentPolicy(id: string) {
const data = await res.json();
if (!data.item) {
if (data.message.includes('already exists')) {
// use regex to get the id from the error message, id is the first string in single quotes
const idRegex = /'([^']+)'/;
const idMatch = data.message.match(idRegex);
if (!idMatch || !idMatch[1]) {
logger.error('Cannot extract id from error message, API response: ' + JSON.stringify(data));
process.exit(1);
}
logger.info(`Agent policy ${idMatch[1]} already exists, using existing policy`);
return getAgentPolicy(idMatch![1]);
}
logger.error('Agent policy not created, API response: ' + JSON.stringify(data));
process.exit(1);
}
return data;
}
async function bumpAgentPolicyRevision(id: string, policy: any) {
const res = await fetch(`${kibanaUrl}/api/fleet/agent_policies/${id}`, {
method: 'put',
body: JSON.stringify({
...omit(policy, [
'id',
'updated_at',
'updated_by',
'revision',
'status',
'schema_version',
'package_policies',
'agents',
]),
monitoring_enabled: ['logs'], // change monitoring to add a revision
}),
headers: {
Authorization: kbnAuth,
'Content-Type': 'application/json',
'kbn-xsrf': 'kibana',
'x-elastic-product-origin': 'fleet',
},
});
const data = await res.json();
if (!data.item) {
logger.error('Agent policy not updated, API response: ' + JSON.stringify(data));
process.exit(1);
}
return data;
}
function logStatusMap(statusMap: Partial<{ [status in AgentStatus]: number }>) {
const statuses = Object.keys(statusMap);
logger.info(
@ -337,14 +409,24 @@ export async function run() {
Array(currentBatchSize)
.fill(0)
.map(async (__, i) => {
const agentPolicyId = uuidv4();
const agentPolicy = await createAgentPolicy(agentPolicyId);
let agentPolicyId = uuidv4();
const agentPolicy = await createAgentPolicy(agentPolicyId, `Policy ${i}`);
agentPolicyId = agentPolicy.item.id;
logger.info(`Created agent policy ${agentPolicy.item.id}`);
const statusMap = statusesArg.reduce((acc, status) => ({ ...acc, [status]: count }), {});
logStatusMap(statusMap);
const agents = createAgentsWithStatuses(statusMap, agentPolicyId, agentVersion);
const agents = createAgentsWithStatuses(
statusMap,
agentPolicyId,
agentVersion,
i > 0 ? `batch-${i}` : undefined
);
const createRes = await createAgentDocsBulk(agents);
if (outdatedArg) {
logger.info(`Bumping agent policy revision so that agents will have outdated policies`);
bumpAgentPolicyRevision(agentPolicyId, agentPolicy.item);
}
logger.info(
`Batch complete, created ${createRes.items.length} agent docs, took ${createRes.took}, errors: ${createRes.errors}`
);