Skip to content
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

[APM] Agent remote config: validation for Java agent configs #63956

Merged
merged 18 commits into from
May 4, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@

import { getBytesRt } from './bytes_rt';
import { isRight } from 'fp-ts/lib/Either';
import { PathReporter } from 'io-ts/lib/PathReporter';

describe('bytesRt', () => {
describe('must accept any amount and unit', () => {
const bytesRt = getBytesRt({
units: ['b', 'mb', 'kb']
});
const bytesRt = getBytesRt({});
describe('it should not accept', () => {
['mb', 1, '1', '5gb', '6tb'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand All @@ -30,8 +29,7 @@ describe('bytesRt', () => {
});
describe('must be at least 0b', () => {
const bytesRt = getBytesRt({
min: '0b',
units: ['b', 'mb', 'kb']
min: '0b'
});

describe('it should not accept', () => {
Expand All @@ -42,6 +40,17 @@ describe('bytesRt', () => {
});
});

describe('it should return correct error message', () => {
['-1kb', '5gb', '6tb'].map(input => {
it(`${JSON.stringify(input)}`, () => {
const result = bytesRt.decode(input);
const message = PathReporter.report(result)[0];
expect(message).toEqual('Must be greater than 0b');
expect(isRight(result)).toBeFalsy();
});
});
});

describe('it should accept', () => {
['1b', '2kb', '3mb'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand All @@ -50,11 +59,10 @@ describe('bytesRt', () => {
});
});
});
describe('must be between 500b and 1mb', () => {
describe('must be between 500b and 1kb', () => {
const bytesRt = getBytesRt({
min: '500b',
max: '1kb',
units: ['b', 'mb', 'kb']
max: '1kb'
});
describe('it should not accept', () => {
['mb', '-1b', '1b', '499b', '1025b', '2kb', '1mb'].map(input => {
Expand All @@ -63,6 +71,16 @@ describe('bytesRt', () => {
});
});
});
describe('it should return correct error message', () => {
['-1b', '1b', '499b', '1025b', '2kb', '1mb'].map(input => {
it(`${JSON.stringify(input)}`, () => {
const result = bytesRt.decode(input);
const message = PathReporter.report(result)[0];
expect(message).toEqual('Must be between 500b and 1kb');
expect(isRight(result)).toBeFalsy();
});
});
});
describe('it should accept', () => {
['500b', '1024b', '1kb'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,70 +6,47 @@

import * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';
import { i18n } from '@kbn/i18n';
import { amountAndUnitToObject } from '../amount_and_unit';
import { getRangeType } from './get_range_type';
import { getRangeTypeMessage } from './get_range_type';

function toBytes(amount: number, unit: string) {
switch (unit) {
case 'b':
return amount;
case 'kb':
return amount * 2 ** 10;
case 'mb':
return amount * 2 ** 20;
case 'b':
default:
return amount;
}
}

export function getBytesRt({
min,
max,
units
}: {
min?: string;
max?: string;
units: string[];
}) {
const { amount: minAmount, unit: minUnit } = min
? amountAndUnitToObject(min)
: { amount: -Infinity, unit: 'b' };

const { amount: maxAmount, unit: maxUnit } = max
? amountAndUnitToObject(max)
: { amount: Infinity, unit: 'mb' };

const message = i18n.translate('xpack.apm.agentConfig.bytes.errorText', {
defaultMessage: `{rangeType, select,
between {Must be between {min} and {max} with unit: {units}}
gt {Must be greater than {min} with unit: {units}}
lt {Must be less than {max} with unit: {units}}
other {Must be an integer with unit: {units}}
}`,
values: {
min,
max,
units: units.join(', '),
rangeType: getRangeType(minAmount, maxAmount)
function amountAndUnitToBytes(value?: string): number | undefined {
if (value) {
const { amount, unit } = amountAndUnitToObject(value);
if (isFinite(amount) && unit) {
return toBytes(amount, unit);
}
});
}
}

export function getBytesRt({ min, max }: { min?: string; max?: string }) {
const minAsBytes = amountAndUnitToBytes(min) ?? -Infinity;
const maxAsBytes = amountAndUnitToBytes(max) ?? Infinity;
const message = getRangeTypeMessage(min, max);

return new t.Type<string, string, unknown>(
'bytesRt',
t.string.is,
(input, context) => {
return either.chain(t.string.validate(input, context), inputAsString => {
const { amount, unit } = amountAndUnitToObject(inputAsString);
const isValidUnit = units.includes(unit);

const inputAsBytes = toBytes(amount, unit);
const minAsBytes = toBytes(minAmount, minUnit);
const maxAsBytes = toBytes(maxAmount, maxUnit);
const inputAsBytes = amountAndUnitToBytes(inputAsString);

const isValidAmount =
inputAsBytes >= minAsBytes && inputAsBytes <= maxAsBytes;
typeof inputAsBytes !== 'undefined' &&
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved
inputAsBytes >= minAsBytes &&
inputAsBytes <= maxAsBytes;

return isValidUnit && isValidAmount
return isValidAmount
? t.success(inputAsString)
: t.failure(input, context, message);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

import { getDurationRt } from './duration_rt';
import { isRight } from 'fp-ts/lib/Either';
import { PathReporter } from 'io-ts/lib/PathReporter';

describe('getDurationRt', () => {
const units = ['ms', 's', 'm'];
describe('must be at least 1m', () => {
const customDurationRt = getDurationRt({ min: '1m', units });
const customDurationRt = getDurationRt({ min: '1m' });
describe('it should not accept', () => {
[
undefined,
Expand All @@ -30,6 +30,16 @@ describe('getDurationRt', () => {
});
});
});
describe('it should return correct error message', () => {
['0m', '-1m', '1ms', '1s'].map(input => {
it(`${JSON.stringify(input)}`, () => {
const result = customDurationRt.decode(input);
const message = PathReporter.report(result)[0];
expect(message).toEqual('Must be greater than 1m');
expect(isRight(result)).toBeFalsy();
});
});
});
describe('it should accept', () => {
['1m', '2m', '1000m'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand All @@ -40,7 +50,7 @@ describe('getDurationRt', () => {
});

describe('must be between 1ms and 1s', () => {
const customDurationRt = getDurationRt({ min: '1ms', max: '1s', units });
const customDurationRt = getDurationRt({ min: '1ms', max: '1s' });

describe('it should not accept', () => {
[
Expand All @@ -65,6 +75,16 @@ describe('getDurationRt', () => {
});
});
});
describe('it should return correct error message', () => {
['-1s', '0s', '2s', '1001ms', '0ms', '-1ms', '0m', '1m'].map(input => {
it(`${JSON.stringify(input)}`, () => {
const result = customDurationRt.decode(input);
const message = PathReporter.report(result)[0];
expect(message).toEqual('Must be between 1ms and 1s');
expect(isRight(result)).toBeFalsy();
});
});
});
describe('it should accept', () => {
['1s', '1ms', '50ms', '1000ms'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand All @@ -74,7 +94,7 @@ describe('getDurationRt', () => {
});
});
describe('must be max 1m', () => {
const customDurationRt = getDurationRt({ max: '1m', units });
const customDurationRt = getDurationRt({ max: '1m' });

describe('it should not accept', () => {
[undefined, null, '', 0, 'foo', true, false, '2m', '61s', '60001ms'].map(
Expand All @@ -85,6 +105,16 @@ describe('getDurationRt', () => {
}
);
});
describe('it should return correct error message', () => {
['2m', '61s', '60001ms'].map(input => {
it(`${JSON.stringify(input)}`, () => {
const result = customDurationRt.decode(input);
const message = PathReporter.report(result)[0];
expect(message).toEqual('Must be less than 1m');
expect(isRight(result)).toBeFalsy();
});
});
});
describe('it should accept', () => {
['1m', '0m', '-1m', '60s', '6000ms', '1ms', '1s'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,40 @@
import * as t from 'io-ts';
import { either } from 'fp-ts/lib/Either';
import moment, { unitOfTime } from 'moment';
import { i18n } from '@kbn/i18n';
import { amountAndUnitToObject, AmountAndUnit } from '../amount_and_unit';
import { getRangeType } from './get_range_type';
import { getRangeTypeMessage } from './get_range_type';

function getDuration({ amount, unit }: AmountAndUnit) {
function toMilliseconds({ amount, unit }: AmountAndUnit) {
return moment.duration(amount, unit as unitOfTime.Base);
}
cauemarcondes marked this conversation as resolved.
Show resolved Hide resolved

export function getDurationRt({
min,
max,
units
}: {
min?: string;
max?: string;
units: string[];
}) {
const minAmountAndUnit = min && amountAndUnitToObject(min);
const maxAmountAndUnit = max && amountAndUnitToObject(max);

const message = i18n.translate('xpack.apm.agentConfig.duration.errorText', {
defaultMessage: `{rangeType, select,
between {Must be between {min} and {max}}
gt {Must be greater than {min}}
lt {Must be less than {max}}
other {Must be an integer}
}`,
values: {
min,
max,
rangeType: getRangeType(
minAmountAndUnit ? minAmountAndUnit.amount : undefined,
maxAmountAndUnit ? maxAmountAndUnit.amount : undefined
)
function amountAndUnitToMilliseconds(value?: string) {
if (value) {
const { amount, unit } = amountAndUnitToObject(value);
if (isFinite(amount) && unit) {
return toMilliseconds({ amount, unit });
}
});
}
}

export function getDurationRt({ min, max }: { min?: string; max?: string }) {
const minAsMilliseconds = amountAndUnitToMilliseconds(min) ?? -Infinity;
const maxAsMilliseconds = amountAndUnitToMilliseconds(max) ?? Infinity;
const message = getRangeTypeMessage(min, max);

return new t.Type<string, string, unknown>(
'durationRt',
t.string.is,
(input, context) => {
return either.chain(t.string.validate(input, context), inputAsString => {
const { amount, unit } = amountAndUnitToObject(inputAsString);
const inputDuration = getDuration({ amount, unit });

const minDuration = minAmountAndUnit
? getDuration(minAmountAndUnit)
: inputDuration;

const maxDuration = maxAmountAndUnit
? getDuration(maxAmountAndUnit)
: inputDuration;
const inputAsMilliseconds = amountAndUnitToMilliseconds(inputAsString);

const isValidUnit = units.includes(unit);
const isValidAmount =
inputDuration >= minDuration && inputDuration <= maxDuration;
typeof inputAsMilliseconds !== 'undefined' &&
inputAsMilliseconds >= minAsMilliseconds &&
inputAsMilliseconds <= maxAsMilliseconds;

return isValidUnit && isValidAmount
return isValidAmount
? t.success(inputAsString)
: t.failure(input, context, message);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
*/

import { isFinite } from 'lodash';
import { i18n } from '@kbn/i18n';
import { amountAndUnitToObject } from '../amount_and_unit';

export function getRangeType(min?: number, max?: number) {
function getRangeType(min?: number, max?: number) {
if (isFinite(min) && isFinite(max)) {
return 'between';
} else if (isFinite(min)) {
Expand All @@ -15,3 +17,25 @@ export function getRangeType(min?: number, max?: number) {
return 'lt'; // less than
}
}

export function getRangeTypeMessage(
min?: number | string,
max?: number | string
) {
return i18n.translate('xpack.apm.agentConfig.range.errorText', {
defaultMessage: `{rangeType, select,
between {Must be between {min} and {max}}
gt {Must be greater than {min}}
lt {Must be less than {max}}
other {Must be an integer}
}`,
values: {
min,
max,
rangeType: getRangeType(
typeof min === 'string' ? amountAndUnitToObject(min).amount : min,
typeof max === 'string' ? amountAndUnitToObject(max).amount : max
)
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { getIntegerRt } from './integer_rt';
import { isRight } from 'fp-ts/lib/Either';
import { PathReporter } from 'io-ts/lib/PathReporter';

describe('getIntegerRt', () => {
describe('with range', () => {
Expand All @@ -24,6 +25,17 @@ describe('getIntegerRt', () => {
);
});

describe('it should return correct error message', () => {
['-1', '-55', '33000'].map(input => {
it(`${JSON.stringify(input)}`, () => {
const result = integerRt.decode(input);
const message = PathReporter.report(result)[0];
expect(message).toEqual('Must be between 0 and 32000');
expect(isRight(result)).toBeFalsy();
});
});
});

describe('it should accept number between 0 and 32000', () => {
['0', '1000', '32000'].map(input => {
it(`${JSON.stringify(input)}`, () => {
Expand Down
Loading