From c0425fb635ff74d7aea91a286e59b60af75b6787 Mon Sep 17 00:00:00 2001 From: Jen Huang Date: Wed, 20 Nov 2024 09:41:50 -0800 Subject: [PATCH] [UII] Expose advanced file logging config in UI (#200274) ## Summary Resolves [#192237](https://github.com/elastic/kibana/issues/192237). This PR exposes the following Elastic Agent file logging configuration options in the agent policy advanced settings UI: ``` agent.logging.to_files agent.logging.files.rotateeverybytes agent.logging.files.keepfiles agent.logging.files.interval ``` image This PR also does some clean up on the default values for all these configured advanced settings so that when user has not touched them, the default values do not get written into the agent policy saved object. [More info here](https://github.com/elastic/kibana/pull/200274#discussion_r1849142612). It also fixes adds missing response schemas for the advanced settings. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 0aa63a7eccea009174db782c57577226ea252bff) # Conflicts: # oas_docs/bundle.json # oas_docs/bundle.serverless.json # oas_docs/output/kibana.serverless.yaml # oas_docs/output/kibana.yaml # x-pack/plugins/fleet/server/routes/agent_policy/index.test.ts # x-pack/plugins/fleet/server/services/form_settings/form_settings.ts # x-pack/plugins/fleet/server/types/models/agent_policy.ts --- .../common/settings/agent_policy_settings.tsx | 78 ++++++++++++++++++- .../fleet/common/types/models/agent_policy.ts | 12 +++ .../components/form_settings/index.test.tsx | 29 +++++++ .../fleet/components/form_settings/index.tsx | 32 +++++++- .../form_settings/settings_field_wrapper.tsx | 22 ++++-- .../agent_policies/full_agent_policy.test.ts | 10 ++- .../form_settings/form_settings.test.ts | 4 +- .../services/form_settings/form_settings.ts | 48 ++++-------- 8 files changed, 185 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx index a4b41979840b2..55a5885fa4e6b 100644 --- a/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx +++ b/x-pack/plugins/fleet/common/settings/agent_policy_settings.tsx @@ -40,7 +40,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ api_field: { name: 'agent_limits_go_max_procs', }, - schema: z.number().int().min(0).default(0), + schema: z.number().int().min(0), }, { name: 'agent.download.timeout', @@ -59,7 +59,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ api_field: { name: 'agent_download_timeout', }, - schema: zodStringWithDurationValidation.default('2h'), + schema: zodStringWithDurationValidation, }, { name: 'agent.download.target_directory', @@ -103,7 +103,7 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ ), learnMoreLink: 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', - schema: zodStringWithDurationValidation.default('30s'), + schema: zodStringWithDurationValidation, }, { name: 'agent.logging.level', @@ -124,4 +124,76 @@ export const AGENT_POLICY_ADVANCED_SETTINGS: SettingsConfig[] = [ 'https://www.elastic.co/guide/en/fleet/current/agent-policy.html#agent-policy-log-level', schema: z.enum(AGENT_LOG_LEVELS).default(DEFAULT_LOG_LEVEL), }, + { + name: 'agent.logging.to_files', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingToFilesTitle', { + defaultMessage: 'Agent logging to files', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_to_files', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.boolean().default(true), + }, + { + name: 'agent.logging.files.rotateeverybytes', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileSizeTitle', { + defaultMessage: 'Agent logging file size limit', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_files_rotateeverybytes', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.number().int().min(0), + }, + { + name: 'agent.logging.files.keepfiles', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileLimitTitle', { + defaultMessage: 'Agent logging number of files', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_files_keepfiles', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: z.number().int().min(0), + }, + { + name: 'agent.logging.files.interval', + title: i18n.translate('xpack.fleet.settings.agentPolicyAdvanced.agentLoggingFileIntervalitle', { + defaultMessage: 'Agent logging number of files', + }), + description: ( + + ), + api_field: { + name: 'agent_logging_files_interval', + }, + learnMoreLink: + 'https://www.elastic.co/guide/en/fleet/current/elastic-agent-standalone-logging-config.html#elastic-agent-standalone-logging-settings', + schema: zodStringWithDurationValidation, + }, ]; diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 72ba130e77379..b33b5b71b8c76 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -182,6 +182,18 @@ export interface FullAgentPolicy { uninstall_token_hash: string; signing_key: string; }; + logging?: { + level?: string; + to_files?: boolean; + files?: { + rotateeverybytes?: number; + keepfiles?: number; + interval?: string; + }; + }; + limits?: { + go_max_procs?: number; + }; }; secret_references?: PolicySecretReference[]; signed?: { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx index 8bafc124ec36b..768f9f913607d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.test.tsx @@ -102,6 +102,35 @@ describe('ConfiguredSettings', () => { expect(mockUpdateAdvancedSettingsHasErrors).toHaveBeenCalledWith(true); }); + it('should render boolean field using checkbox', () => { + const result = render([ + { + name: 'agent.logging.to_files', + title: 'Agent logging to files', + description: 'Description', + learnMoreLink: '', + api_field: { + name: 'agent_logging_to_files', + }, + schema: z.boolean().default(false), + }, + ]); + + expect(result.getByText('Agent logging to files')).not.toBeNull(); + const input = result.getByTestId('configuredSetting-agent.logging.to_files'); + expect(input).not.toBeChecked(); + + act(() => { + fireEvent.click(input); + }); + + expect(mockUpdateAgentPolicy).toHaveBeenCalledWith( + expect.objectContaining({ + advanced_settings: expect.objectContaining({ agent_logging_to_files: true }), + }) + ); + }); + it('should not render field if hidden', () => { const result = render([ { diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx index 7ebf80141c554..e93dd0b6cdd82 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/index.tsx @@ -7,7 +7,9 @@ import { ZodFirstPartyTypeKind } from '@kbn/zod'; import React from 'react'; -import { EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui'; +import { EuiCheckbox, EuiFieldNumber, EuiFieldText, EuiSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; import type { SettingsConfig } from '../../../../../common/settings/types'; @@ -68,7 +70,7 @@ settingComponentRegistry.set(ZodFirstPartyTypeKind.ZodEnum, ({ disabled, ...sett ( { + return ( + ( + + )} + /> + ); + } +); + export function ConfiguredSettings({ configuredSettings, disabled, @@ -101,7 +127,7 @@ export function ConfiguredSettings({ const Component = settingComponentRegistry.get(getInnerType(configuredSetting.schema)); if (!Component) { - throw new Error(`Unknown setting type: ${configuredSetting.schema._type}}`); + throw new Error(`Unknown setting type: ${configuredSetting.schema._type}`); } return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx index 61adef4729a27..1885d466711fb 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/components/form_settings/settings_field_wrapper.tsx @@ -13,12 +13,15 @@ import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import type { SettingsConfig } from '../../../../../common/settings/types'; import { useAgentPolicyFormContext } from '../../sections/agent_policy/components/agent_policy_form'; -export const convertValue = (value: string, type: keyof typeof ZodFirstPartyTypeKind): any => { +export const convertValue = ( + value: string | boolean, + type: keyof typeof ZodFirstPartyTypeKind +): any => { if (type === ZodFirstPartyTypeKind.ZodNumber) { if (value === '') { return 0; } - return parseInt(value, 10); + return parseInt(value as string, 10); } return value; }; @@ -48,7 +51,8 @@ export const SettingsFieldWrapper: React.FC<{ const coercedSchema = settingsConfig.schema as z.ZodString; const handleChange = (e: React.ChangeEvent) => { - const newValue = convertValue(e.target.value, typeName); + const value = typeName === ZodFirstPartyTypeKind.ZodBoolean ? e.target.checked : e.target.value; + const newValue = convertValue(value, typeName); const validationError = validateSchema(coercedSchema, newValue); if (validationError) { @@ -97,9 +101,13 @@ export const SettingsFieldWrapper: React.FC<{ }; export const getInnerType = (schema: z.ZodType) => { - return schema instanceof z.ZodDefault - ? schema._def.innerType._def.typeName === 'ZodEffects' + if (schema._def.innerType) { + return schema._def.innerType._def.typeName === 'ZodEffects' ? schema._def.innerType._def.schema._def.typeName - : schema._def.innerType._def.typeName - : schema._def.typeName; + : schema._def.innerType._def.typeName; + } + if (schema._def.typeName === 'ZodEffects') { + return schema._def.schema._def.typeName; + } + return schema._def.typeName; }; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index fa5522d50802b..b62724b0cea50 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -888,6 +888,10 @@ describe('getFullAgentPolicy', () => { advanced_settings: { agent_limits_go_max_procs: 2, agent_logging_level: 'debug', + agent_logging_to_files: true, + agent_logging_files_rotateeverybytes: 10000, + agent_logging_files_keepfiles: 10, + agent_logging_files_interval: '7h', }, }); const agentPolicy = await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); @@ -896,7 +900,11 @@ describe('getFullAgentPolicy', () => { id: 'agent-policy', agent: { limits: { go_max_procs: 2 }, - logging: { level: 'debug' }, + logging: { + level: 'debug', + to_files: true, + files: { rotateeverybytes: 10000, keepfiles: 10, interval: '7h' }, + }, }, }); }); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts index afcba824f1a33..710d862bf3ad1 100644 --- a/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.test.ts @@ -55,10 +55,10 @@ describe('form_settings', () => { ).not.toThrow(); }); - it('generate a valid API schema for api_field with default value', () => { + it('generate a valid API schema for api_field with default value but not add the value', () => { const apiSchema = schema.object(_getSettingsAPISchema(TEST_SETTINGS)); const res = apiSchema.validate({ advanced_settings: {} }); - expect(res).toEqual({ advanced_settings: { test_foo_default_value: 'test' } }); + expect(res).toEqual({ advanced_settings: {} }); }); }); diff --git a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts index a381fcd55b4ba..2c625f6bd0574 100644 --- a/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts +++ b/x-pack/plugins/fleet/server/services/form_settings/form_settings.ts @@ -24,39 +24,19 @@ export function _getSettingsAPISchema(settings: SettingsConfig[]): Props { if (!setting.api_field) { return; } - const defaultValueRes = setting.schema.safeParse(undefined); - const defaultValue = defaultValueRes.success ? defaultValueRes.data : undefined; - if (defaultValue) { - validations[setting.api_field.name] = schema.oneOf( - [ - schema.any({ - validate: (val: any) => { - const res = setting.schema.safeParse(val); - if (!res.success) { - return stringifyZodError(res.error); - } - }, - }), - schema.literal(null), - ], - { - defaultValue, - } - ); - } else { - validations[setting.api_field.name] = schema.maybe( - schema.nullable( - schema.any({ - validate: (val: any) => { - const res = setting.schema.safeParse(val); - if (!res.success) { - return stringifyZodError(res.error); - } - }, - }) - ) - ); - } + validations[setting.api_field.name] = schema.maybe( + schema.oneOf([ + schema.literal(null), + schema.any({ + validate: (val: any) => { + const res = setting.schema.safeParse(val); + if (!res.success) { + return stringifyZodError(res.error); + } + }, + }), + ]) + ); }); const advancedSettingsValidations: Props = { @@ -89,7 +69,7 @@ export function _getSettingsValuesForAgentPolicy( } const val = agentPolicy.advanced_settings?.[setting.api_field.name]; - if (val) { + if (val !== undefined) { settingsValues[setting.name] = val; } });