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

[PoC] Using config-schema for SLO API validation #184983

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions packages/kbn-server-route-repository/src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type ServerRoute<
? {
endpoint: TEndpoint;
params?: TRouteParamsRT;
validation?: any; // config-schema
handler: ({}: TRouteHandlerResources &
(TRouteParamsRT extends RouteParamsRT
? DecodedRequestParamsOfType<TRouteParamsRT>
Expand Down
52 changes: 49 additions & 3 deletions x-pack/packages/kbn-slo-schema/src/rest_specs/routes/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import * as t from 'io-ts';
import { indicatorSchema, timeWindowSchema } from '../../schema';
import { schema, TypeOf } from '@kbn/config-schema';
import {
allOrAnyStringOrArrayConfigSchema,
indicatorSchema,
indicatorConfigSchema,
timeWindowConfigSchema,
timeWindowSchema,
} from '../../schema';
import { allOrAnyStringOrArray } from '../../schema/common';
import {
budgetingMethodSchema,
budgetingMethodConfigSchema,
objectiveSchema,
objectiveConfigSchema,
optionalSettingsSchema,
optionalSettingsConfigSchema,
sloIdSchema,
sloIdConfigSchema,
tagsSchema,
tagsConfigSchema,
} from '../../schema/slo';

const createSLOParamsSchema = t.type({
Expand All @@ -35,13 +48,46 @@ const createSLOParamsSchema = t.type({
]),
});

const createSLOParamsConfigSchema = {
body: schema.object({
name: schema.string(),
description: schema.string(),
indicator: indicatorConfigSchema,
timeWindow: timeWindowConfigSchema,
budgetingMethod: budgetingMethodConfigSchema,
objective: objectiveConfigSchema,
id: schema.maybe(sloIdConfigSchema),
settings: schema.maybe(optionalSettingsConfigSchema),
tags: schema.maybe(tagsConfigSchema),
groupBy: schema.maybe(allOrAnyStringOrArrayConfigSchema),
revision: schema.maybe(schema.number()),
}),
};

const createSLOResponseSchema = t.type({
id: sloIdSchema,
});

const createSLOResponseConfigSchema = {
id: sloIdConfigSchema,
};

type CreateSLOInput = t.OutputOf<typeof createSLOParamsSchema.props.body>; // Raw payload sent by the frontend
type CreateSLOParams = t.TypeOf<typeof createSLOParamsSchema.props.body>; // Parsed payload used by the backend
type CreateSLOInputConfigSchema = TypeOf<typeof createSLOParamsConfigSchema.body>; // Parsed payload used by the frontend
type CreateSLOParamsConfigSchema = TypeOf<typeof createSLOParamsConfigSchema.body>; // Parsed payload used by the backend
type CreateSLOResponse = t.TypeOf<typeof createSLOResponseSchema>; // Raw response sent to the frontend

export { createSLOParamsSchema, createSLOResponseSchema };
export type { CreateSLOInput, CreateSLOParams, CreateSLOResponse };
export {
createSLOParamsSchema,
createSLOParamsConfigSchema,
createSLOResponseSchema,
createSLOResponseConfigSchema,
};
export type {
CreateSLOInput,
CreateSLOInputConfigSchema,
CreateSLOParams,
CreateSLOParamsConfigSchema,
CreateSLOResponse,
};
12 changes: 10 additions & 2 deletions x-pack/packages/kbn-slo-schema/src/rest_specs/routes/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { schema } from '@kbn/config-schema';
import * as t from 'io-ts';
import { sloIdSchema } from '../../schema/slo';
import { sloIdSchema, sloIdConfigSchema } from '../../schema/slo';

const deleteSLOParamsSchema = t.type({
path: t.type({
id: sloIdSchema,
}),
});

export { deleteSLOParamsSchema };
const deleteSLOParamsConfigSchema = {
params: schema.object({
id: sloIdConfigSchema,
}),
};

export { deleteSLOParamsSchema, deleteSLOParamsConfigSchema };
10 changes: 10 additions & 0 deletions x-pack/packages/kbn-slo-schema/src/schema/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@

import { either } from 'fp-ts/lib/Either';
import * as t from 'io-ts';
import { schema } from '@kbn/config-schema';

const ALL_VALUE = '*';

const allOrAnyString = t.union([t.literal(ALL_VALUE), t.string]);
const allOrAnyStringConfigSchema = schema.oneOf([schema.literal(ALL_VALUE), schema.string()]);

const allOrAnyStringOrArray = t.union([
t.literal(ALL_VALUE),
t.string,
t.array(t.union([t.literal(ALL_VALUE), t.string])),
]);

const allOrAnyStringOrArrayConfigSchema = schema.oneOf([
schema.literal(ALL_VALUE),
schema.string(),
schema.arrayOf(schema.oneOf([schema.literal(ALL_VALUE), schema.string()])),
]);

const dateType = new t.Type<Date, string, unknown>(
'DateType',
(input: unknown): input is Date => input instanceof Date,
Expand Down Expand Up @@ -94,7 +102,9 @@ const dateRangeSchema = t.type({
export {
ALL_VALUE,
allOrAnyString,
allOrAnyStringConfigSchema,
allOrAnyStringOrArray,
allOrAnyStringOrArrayConfigSchema,
dateRangeSchema,
dateType,
errorBudgetSchema,
Expand Down
21 changes: 20 additions & 1 deletion x-pack/packages/kbn-slo-schema/src/schema/duration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { either } from 'fp-ts/lib/Either';
import * as t from 'io-ts';
import { schema } from '@kbn/config-schema';

import { Duration, DurationUnit } from '../models/duration';

Expand All @@ -28,4 +29,22 @@ const durationType = new t.Type<Duration, string, unknown>(
(duration: Duration): string => duration.format()
);

export { durationType };
const durationTypeConfigSchema = schema.string({
minLength: 2,
validate(value) {
try {
new Duration(parseInt(value.slice(0, -1), 10), value.slice(-1) as DurationUnit);
return;
} catch (err) {
// IF duration is not valid, the get the following message:
// {
// "statusCode": 400,
// "error": "Bad Request",
// "message": "[request body.timeWindow.duration]: error: Error: invalid duration unit"
// }
return `error: ${err}`;
}
},
});

export { durationType, durationTypeConfigSchema };
60 changes: 58 additions & 2 deletions x-pack/packages/kbn-slo-schema/src/schema/indicators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
*/

import * as t from 'io-ts';
import { allOrAnyString } from './common';
import { schema } from '@kbn/config-schema';
import { allOrAnyString, allOrAnyStringConfigSchema } from './common';

const kqlQuerySchema = t.string;
const kqlQueryConfigSchema = schema.string();

const filtersSchema = t.array(
t.type({
Expand All @@ -32,15 +34,44 @@ const filtersSchema = t.array(
query: t.record(t.string, t.any),
})
);
const filtersConfigSchema = schema.arrayOf(
schema.object({
// TODO Change to partial
meta: schema.object({
alias: schema.nullable(schema.string()),
disabled: schema.boolean(),
negate: schema.boolean(),
// controlledBy is there to identify who owns the filter
controlledBy: schema.string(),
// allows grouping of filters
group: schema.string(),
// index and type are optional only because when you create a new filter, there are no defaults
index: schema.string(),
isMultiIndex: schema.boolean(),
type: schema.string(),
key: schema.string(),
params: schema.any(),
value: schema.string(),
field: schema.string(),
}),
query: schema.recordOf(schema.string(), schema.any()),
})
);

const kqlWithFiltersSchema = t.type({
kqlQuery: t.string,
filters: filtersSchema,
});
const kqlWithFiltersConfigSchema = schema.object({
kqlQuery: schema.string(),
filters: filtersConfigSchema,
});

const querySchema = t.union([kqlQuerySchema, kqlWithFiltersSchema]);
const queryConfigSchema = schema.oneOf([kqlQueryConfigSchema, kqlWithFiltersConfigSchema]);

const apmTransactionDurationIndicatorTypeSchema = t.literal('sli.apm.transactionDuration');
const apmTransactionDurationIndicator = 'sli.apm.transactionDuration';
const apmTransactionDurationIndicatorTypeSchema = t.literal(apmTransactionDurationIndicator);
const apmTransactionDurationIndicatorSchema = t.type({
type: apmTransactionDurationIndicatorTypeSchema,
params: t.intersection([
Expand All @@ -57,6 +88,24 @@ const apmTransactionDurationIndicatorSchema = t.type({
}),
]),
});
const apmTransactionDurationIndicatorConfigSchema = schema.object({
type: schema.literal(apmTransactionDurationIndicator),
// TODO Change to intersection
params: schema.oneOf([
schema.object({
environment: allOrAnyStringConfigSchema,
service: allOrAnyStringConfigSchema,
transactionType: allOrAnyStringConfigSchema,
transactionName: allOrAnyStringConfigSchema,
threshold: schema.number(),
index: schema.string(),
}),
// TODO Change to partial
schema.object({
filter: queryConfigSchema,
}),
]),
});

const apmTransactionErrorRateIndicatorTypeSchema = t.literal('sli.apm.transactionErrorRate');
const apmTransactionErrorRateIndicatorSchema = t.type({
Expand Down Expand Up @@ -312,13 +361,19 @@ const indicatorSchema = t.union([
timesliceMetricIndicatorSchema,
histogramIndicatorSchema,
]);
// TODO Add rest of the indicators
const indicatorConfigSchema = schema.oneOf([
apmTransactionDurationIndicatorConfigSchema,
schema.any(),
]);

export {
kqlQuerySchema,
kqlWithFiltersSchema,
querySchema,
filtersSchema,
apmTransactionDurationIndicatorSchema,
apmTransactionDurationIndicatorConfigSchema,
apmTransactionDurationIndicatorTypeSchema,
apmTransactionErrorRateIndicatorSchema,
apmTransactionErrorRateIndicatorTypeSchema,
Expand All @@ -340,6 +395,7 @@ export {
histogramIndicatorTypeSchema,
histogramIndicatorSchema,
indicatorSchema,
indicatorConfigSchema,
indicatorTypesArraySchema,
indicatorTypesSchema,
};
43 changes: 42 additions & 1 deletion x-pack/packages/kbn-slo-schema/src/schema/slo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,40 @@
*/

import * as t from 'io-ts';
import { schema } from '@kbn/config-schema';
import { allOrAnyStringOrArray, dateType } from './common';
import { durationType } from './duration';
import { durationType, durationTypeConfigSchema } from './duration';
import { indicatorSchema } from './indicators';
import { timeWindowSchema } from './time_window';

const occurrencesBudgetingMethodSchema = t.literal('occurrences');
const occurrencesBudgetingMethodConfigSchema = schema.literal('occurrences');
const timeslicesBudgetingMethodSchema = t.literal('timeslices');
const timeslicesBudgetingMethodConfigSchema = schema.literal('timeslices');

const budgetingMethodSchema = t.union([
occurrencesBudgetingMethodSchema,
timeslicesBudgetingMethodSchema,
]);
const budgetingMethodConfigSchema = schema.oneOf([
occurrencesBudgetingMethodConfigSchema,
timeslicesBudgetingMethodConfigSchema,
]);

const targetSchema = t.type({ target: t.number });
const targetConfigSchema = schema.object({ target: schema.number() });

const objectiveSchema = t.intersection([
targetSchema,
t.partial({ timesliceTarget: t.number, timesliceWindow: durationType }),
]);
// TODO Change to intersection
const objectiveConfigSchema = schema.oneOf([
targetConfigSchema,
// TODO Change to partial
// TODO Change to durationType
schema.object({ timesliceTarget: schema.number(), timesliceWindow: schema.duration() }),
]);

const settingsSchema = t.type({
syncDelay: durationType,
Expand All @@ -35,8 +50,27 @@ const settingsSchema = t.type({
const groupBySchema = allOrAnyStringOrArray;

const optionalSettingsSchema = t.partial({ ...settingsSchema.props });
const optionalSettingsConfigSchema = schema.object({
syncDelay: schema.maybe(durationTypeConfigSchema),
frequency: schema.maybe(durationTypeConfigSchema),
preventInitialBackfill: schema.maybe(schema.boolean()),
});

const tagsSchema = t.array(t.string);
const tagsConfigSchema = schema.arrayOf(
schema.string({
meta: {
description: 'An identifier for the slo.',
},
})
);

const sloIdSchema = t.string;
const sloIdConfigSchema = schema.string({
meta: {
description: 'An identifier for the slo.',
},
});

const sloDefinitionSchema = t.type({
id: sloIdSchema,
Expand All @@ -58,14 +92,21 @@ const sloDefinitionSchema = t.type({

export {
budgetingMethodSchema,
budgetingMethodConfigSchema,
objectiveSchema,
objectiveConfigSchema,
groupBySchema,
occurrencesBudgetingMethodSchema,
occurrencesBudgetingMethodConfigSchema,
optionalSettingsSchema,
optionalSettingsConfigSchema,
settingsSchema,
sloDefinitionSchema,
sloIdSchema,
sloIdConfigSchema,
tagsSchema,
tagsConfigSchema,
targetSchema,
timeslicesBudgetingMethodSchema,
timeslicesBudgetingMethodConfigSchema,
};
Loading