diff --git a/docs/monitoring/cluster-alerts.asciidoc b/docs/monitoring/cluster-alerts.asciidoc index 15df791ae274..fce76965dd9a 100644 --- a/docs/monitoring/cluster-alerts.asciidoc +++ b/docs/monitoring/cluster-alerts.asciidoc @@ -46,8 +46,6 @@ To receive email notifications for the Cluster Alerts: 1. Configure an email account as described in {xpack-ref}/actions-email.html#configuring-email[Configuring Email Accounts]. -2. Navigate to the *Management* page in {kib}. -3. Go to the *Advanced Settings* page, find the `xpack:defaultAdminEmail` -setting, and enter your email address. +2. Configure the `xpack.monitoring.cluster_alerts.email_notifications.email_address` setting in `kibana.yml` with your email address. Email notifications are sent only when Cluster Alerts are triggered and resolved. diff --git a/x-pack/plugins/monitoring/__tests__/deprecations.js b/x-pack/plugins/monitoring/__tests__/deprecations.js index 8eb6473cfd31..6b8668b156a8 100644 --- a/x-pack/plugins/monitoring/__tests__/deprecations.js +++ b/x-pack/plugins/monitoring/__tests__/deprecations.js @@ -112,4 +112,66 @@ describe('monitoring plugin deprecations', function () { expect(log.called).to.be(false); }); + describe('cluster_alerts.email_notifications.email_address', function () { + it(`shouldn't log when email notifications are disabled`, function () { + const settings = { + cluster_alerts: { + email_notifications: { + enabled: false + } + } + }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it(`shouldn't log when cluster alerts are disabled`, function () { + const settings = { + cluster_alerts: { + enabled: false, + email_notifications: { + enabled: true + } + } + }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it(`shouldn't log when email_address is specified`, function () { + const settings = { + cluster_alerts: { + enabled: true, + email_notifications: { + enabled: true, + email_address: 'foo@bar.com' + } + } + }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(false); + }); + + it(`should log when email_address is missing, but alerts/notifications are both enabled`, function () { + const settings = { + cluster_alerts: { + enabled: true, + email_notifications: { + enabled: true + } + } + }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + expect(log.called).to.be(true); + }); + }); + }); diff --git a/x-pack/plugins/monitoring/common/constants.js b/x-pack/plugins/monitoring/common/constants.js index 718606d8b6be..7bc8164130ad 100644 --- a/x-pack/plugins/monitoring/common/constants.js +++ b/x-pack/plugins/monitoring/common/constants.js @@ -94,7 +94,7 @@ export const CALCULATE_DURATION_UNTIL = 'until'; /** * In order to show ML Jobs tab in the Elasticsearch section / tab navigation, license must be supported */ -export const ML_SUPPORTED_LICENSES = [ 'trial', 'platinum' ]; +export const ML_SUPPORTED_LICENSES = ['trial', 'platinum']; /** * Metadata service URLs for the different cloud services that have constant URLs (e.g., unlike GCP, which is a constant prefix). @@ -135,7 +135,12 @@ export const DEFAULT_NO_DATA_MESSAGE_WITH_FILTER = ( ); export const TABLE_ACTION_UPDATE_FILTER = 'UPDATE_FILTER'; -export const TABLE_ACTION_RESET_PAGING = 'RESET_PAGING'; +export const TABLE_ACTION_RESET_PAGING = 'RESET_PAGING'; export const DEBOUNCE_SLOW_MS = 17; // roughly how long it takes to render a frame at 60fps export const DEBOUNCE_FAST_MS = 10; // roughly how long it takes to render a frame at 100fps + +/** + * Configuration key for setting the email address used for cluster alert notifications. + */ +export const CLUSTER_ALERTS_ADDRESS_CONFIG_KEY = 'cluster_alerts.email_notifications.email_address'; diff --git a/x-pack/plugins/monitoring/config.js b/x-pack/plugins/monitoring/config.js index 02ac28728206..431aed95182f 100644 --- a/x-pack/plugins/monitoring/config.js +++ b/x-pack/plugins/monitoring/config.js @@ -13,7 +13,7 @@ import { XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS } from '../../server/li */ export const config = (Joi) => { const { array, boolean, number, object, string } = Joi; - const DEFAULT_REQUEST_HEADERS = [ 'authorization' ]; + const DEFAULT_REQUEST_HEADERS = ['authorization']; return object({ ccs: object({ @@ -49,7 +49,8 @@ export const config = (Joi) => { enabled: boolean().default(true), index: string().default('.monitoring-alerts-6'), email_notifications: object({ - enabled: boolean().default(true) + enabled: boolean().default(true), + email_address: string().email(), }).default() }).default(), xpack_api_polling_frequency_millis: number().default(XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS), diff --git a/x-pack/plugins/monitoring/deprecations.js b/x-pack/plugins/monitoring/deprecations.js index 8e882975a29c..a6b91d4a6d60 100644 --- a/x-pack/plugins/monitoring/deprecations.js +++ b/x-pack/plugins/monitoring/deprecations.js @@ -5,6 +5,7 @@ */ import { get, has, set } from 'lodash'; +import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY } from './common/constants'; /** * Re-writes deprecated user-defined config settings and logs warnings as a @@ -29,12 +30,19 @@ export const deprecations = ({ rename }) => { delete settings.elasticsearch.ssl.verify; log('Config key "xpack.monitoring.elasticsearch.ssl.verify" is deprecated. ' + - 'It has been replaced with "xpack.monitoring.elasticsearch.ssl.verificationMode"'); + 'It has been replaced with "xpack.monitoring.elasticsearch.ssl.verificationMode"'); }, (settings, log) => { if (has(settings, 'report_stats')) { log('Config key "xpack.monitoring.report_stats" is deprecated and will be removed in 7.0. ' + - 'Use "xpack.xpack_main.telemetry.enabled" instead.'); + 'Use "xpack.xpack_main.telemetry.enabled" instead.'); + } + }, + (settings, log) => { + const clusterAlertsEnabled = get(settings, 'cluster_alerts.enabled'); + const emailNotificationsEnabled = clusterAlertsEnabled && get(settings, 'cluster_alerts.email_notifications.enabled'); + if (emailNotificationsEnabled && !get(settings, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { + log(`Config key "${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}" will be required for email notifications to work in 7.0."`); } }, ]; diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js index a2bc6f4b9419..4189903684a9 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/check_for_email_value.js @@ -8,27 +8,31 @@ import expect from 'expect.js'; import { checkForEmailValue } from '../get_settings_collector'; describe('getSettingsCollector / checkForEmailValue', () => { + const mockLogger = { + warn: () => { } + }; + it('ignores shouldUseNull=true value and returns email if email value if one is set', async () => { const shouldUseNull = true; const getDefaultAdminEmailMock = () => 'test@elastic.co'; - expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co'); + expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co'); }); it('ignores shouldUseNull=false value and returns email if email value if one is set', async () => { const shouldUseNull = false; const getDefaultAdminEmailMock = () => 'test@elastic.co'; - expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co'); + expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be('test@elastic.co'); }); it('returns a null if no email value is set and null is allowed', async () => { const shouldUseNull = true; const getDefaultAdminEmailMock = () => null; - expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be(null); + expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be(null); }); it('returns undefined if no email value is set and null is not allowed', async () => { const shouldUseNull = false; const getDefaultAdminEmailMock = () => null; - expect(await checkForEmailValue(undefined, undefined, shouldUseNull, getDefaultAdminEmailMock)).to.be(undefined); + expect(await checkForEmailValue(undefined, undefined, mockLogger, shouldUseNull, getDefaultAdminEmailMock)).to.be(undefined); }); }); diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js index 7796cb9621d7..434f6bc41f58 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/__tests__/get_default_admin_email.js @@ -9,16 +9,23 @@ import sinon from 'sinon'; import { set } from 'lodash'; import { XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING } from '../../../../../../server/lib/constants'; -import { getDefaultAdminEmail } from '../get_settings_collector'; +import { getDefaultAdminEmail, resetDeprecationWarning } from '../get_settings_collector'; +import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY } from '../../../../common/constants'; describe('getSettingsCollector / getDefaultAdminEmail', () => { - function setup({ enabled = true, docExists = true, adminEmail = 'admin@email.com' }) { + function setup({ enabled = true, docExists = true, defaultAdminEmail = 'default-admin@email.com', adminEmail = null }) { const config = { get: sinon.stub() }; config.get .withArgs('xpack.monitoring.cluster_alerts.email_notifications.enabled') .returns(enabled); + if (adminEmail) { + config.get + .withArgs(`xpack.monitoring.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}`) + .returns(adminEmail); + } + config.get .withArgs('kibana.index') .returns('.kibana'); @@ -29,8 +36,8 @@ describe('getSettingsCollector / getDefaultAdminEmail', () => { const doc = {}; if (docExists) { - if (adminEmail) { - set(doc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], adminEmail); + if (defaultAdminEmail) { + set(doc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], defaultAdminEmail); } else { set(doc, '_source.config', {}); } @@ -46,41 +53,131 @@ describe('getSettingsCollector / getDefaultAdminEmail', () => { })) .returns(doc); + const log = { + warn: sinon.stub() + }; + return { config, - callCluster + callCluster, + log, }; } - describe('xpack.monitoring.cluster_alerts.email_notifications.enabled = false', () => { - it('returns null', async () => { - const { config, callCluster } = setup({ enabled: false }); - expect(await getDefaultAdminEmail(config, callCluster)).to.be(null); - sinon.assert.notCalled(callCluster); + describe('using xpack:defaultAdminEmail', () => { + beforeEach(() => { + resetDeprecationWarning(); + }); + + describe('xpack.monitoring.cluster_alerts.email_notifications.enabled = false', () => { + + it('returns null', async () => { + const { config, callCluster, log } = setup({ enabled: false }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null); + sinon.assert.notCalled(callCluster); + }); + + it('does not log a deprecation warning', async () => { + const { config, callCluster, log } = setup({ enabled: false }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.notCalled(log.warn); + }); + }); + + describe('doc does not exist', () => { + it('returns null', async () => { + const { config, callCluster, log } = setup({ docExists: false }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null); + sinon.assert.calledOnce(callCluster); + }); + + it('logs a deprecation warning', async () => { + const { config, callCluster, log } = setup({ docExists: false }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.calledOnce(log.warn); + }); + }); + + describe('value is not defined', () => { + it('returns null', async () => { + const { config, callCluster, log } = setup({ defaultAdminEmail: false }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null); + sinon.assert.calledOnce(callCluster); + }); + + it('logs a deprecation warning', async () => { + const { config, callCluster, log } = setup({ defaultAdminEmail: false }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.calledOnce(log.warn); + }); + }); + + describe('value is defined', () => { + it('returns value', async () => { + const { config, callCluster, log } = setup({ defaultAdminEmail: 'hello@world' }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be('hello@world'); + sinon.assert.calledOnce(callCluster); + }); + + it('logs a deprecation warning', async () => { + const { config, callCluster, log } = setup({ defaultAdminEmail: 'hello@world' }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.calledOnce(log.warn); + }); }); }); - describe('doc does not exist', () => { - it('returns null', async () => { - const { config, callCluster } = setup({ docExists: false }); - expect(await getDefaultAdminEmail(config, callCluster)).to.be(null); - sinon.assert.calledOnce(callCluster); + describe('using xpack.monitoring.cluster_alerts.email_notifications.email_address', () => { + beforeEach(() => { + resetDeprecationWarning(); }); - }); - describe('value is not defined', () => { - it('returns null', async () => { - const { config, callCluster } = setup({ adminEmail: false }); - expect(await getDefaultAdminEmail(config, callCluster)).to.be(null); - sinon.assert.calledOnce(callCluster); + describe('xpack.monitoring.cluster_alerts.email_notifications.enabled = false', () => { + it('returns null', async () => { + const { config, callCluster, log } = setup({ enabled: false }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be(null); + sinon.assert.notCalled(callCluster); + }); + + it('does not log a deprecation warning', async () => { + const { config, callCluster, log } = setup({ enabled: false }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.notCalled(log.warn); + }); }); - }); - describe('value is defined', () => { - it('returns value', async () => { - const { config, callCluster } = setup({ adminEmail: 'hello@world' }); - expect(await getDefaultAdminEmail(config, callCluster)).to.be('hello@world'); - sinon.assert.calledOnce(callCluster); + describe('value is not defined', () => { + it('returns value from xpack:defaultAdminEmail', async () => { + const { config, callCluster, log } = setup({ + defaultAdminEmail: 'default-admin@email.com', + adminEmail: false + }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be('default-admin@email.com'); + sinon.assert.calledOnce(callCluster); + }); + + it('logs a deprecation warning', async () => { + const { config, callCluster, log } = setup({ + defaultAdminEmail: 'default-admin@email.com', + adminEmail: false + }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.calledOnce(log.warn); + }); + }); + + describe('value is defined', () => { + it('returns value', async () => { + const { config, callCluster, log } = setup({ adminEmail: 'hello@world' }); + expect(await getDefaultAdminEmail(config, callCluster, log)).to.be('hello@world'); + sinon.assert.notCalled(callCluster); + }); + + it('does not log a deprecation warning', async () => { + const { config, callCluster, log } = setup({ adminEmail: 'hello@world' }); + await getDefaultAdminEmail(config, callCluster, log); + sinon.assert.notCalled(log.warn); + }); }); }); }); diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js index c0793a4bb6b9..0deafc2c4982 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js @@ -6,24 +6,48 @@ import { get } from 'lodash'; import { XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING } from '../../../../../server/lib/constants'; -import { KIBANA_SETTINGS_TYPE } from '../../../common/constants'; +import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY, KIBANA_SETTINGS_TYPE } from '../../../common/constants'; + +let loggedDeprecationWarning = false; + +export function resetDeprecationWarning() { + loggedDeprecationWarning = false; +} /* * Check if Cluster Alert email notifications is enabled in config * If so, use uiSettings API to fetch the X-Pack default admin email */ -export async function getDefaultAdminEmail(config, callCluster) { +export async function getDefaultAdminEmail(config, callCluster, log) { if (!config.get('xpack.monitoring.cluster_alerts.email_notifications.enabled')) { return null; } + const emailAddressConfigKey = `xpack.monitoring.${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}`; + const configuredEmailAddress = config.get(emailAddressConfigKey); + + if (configuredEmailAddress) { + return configuredEmailAddress; + } + + // DEPRECATED (Remove below in 7.0): If an email address is not configured in kibana.yml, then fallback to xpack:defaultAdminEmail + if (!loggedDeprecationWarning) { + const message = ( + `Monitoring is using ${XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING} for cluster alert notifications, ` + + `which will not be supported in Kibana 7.0. Please configure ${emailAddressConfigKey} in your kibana.yml settings` + ); + + log.warn(message); + loggedDeprecationWarning = true; + } + const index = config.get('kibana.index'); const version = config.get('pkg.version'); const uiSettingsDoc = await callCluster('get', { index, type: 'doc', id: `config:${version}`, - ignore: [ 400, 404 ] // 400 if the index is closed, 404 if it does not exist + ignore: [400, 404] // 400 if the index is closed, 404 if it does not exist }); return get(uiSettingsDoc, ['_source', 'config', XPACK_DEFAULT_ADMIN_EMAIL_UI_SETTING], null); @@ -35,10 +59,11 @@ let shouldUseNull = true; export async function checkForEmailValue( config, callCluster, + log, _shouldUseNull = shouldUseNull, _getDefaultAdminEmail = getDefaultAdminEmail ) { - const defaultAdminEmail = await _getDefaultAdminEmail(config, callCluster); + const defaultAdminEmail = await _getDefaultAdminEmail(config, callCluster, log); // Allow null so clearing the advanced setting will be reflected in the data const isAcceptableNull = defaultAdminEmail === null && _shouldUseNull; @@ -61,7 +86,7 @@ export function getSettingsCollector(server) { type: KIBANA_SETTINGS_TYPE, async fetch(callCluster) { let kibanaSettingsData; - const defaultAdminEmail = await checkForEmailValue(config, callCluster); + const defaultAdminEmail = await checkForEmailValue(config, callCluster, this.log); // skip everything if defaultAdminEmail === undefined if (defaultAdminEmail || (defaultAdminEmail === null && shouldUseNull)) {