@l.x/datadog-cloud 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTRUMENTATION_REPORT.md +217 -0
- package/LICENSE +122 -0
- package/Pulumi.dev-portal-prod.yaml +10 -0
- package/Pulumi.yaml +6 -0
- package/config.ts +243 -0
- package/dashboards/Pulumi.prod.yaml +6 -0
- package/dashboards/Pulumi.yaml +6 -0
- package/dashboards/config.ts +30 -0
- package/dashboards/dashboard-factory.ts +187 -0
- package/dashboards/dashboard-types.ts +127 -0
- package/dashboards/definitions/dev-portal/index.ts +1 -0
- package/dashboards/definitions/dev-portal/service-dashboard.ts +169 -0
- package/dashboards/definitions/index.ts +1 -0
- package/dashboards/esc/shared.yaml +7 -0
- package/dashboards/index.ts +40 -0
- package/dashboards/package.json +20 -0
- package/esc/dev-portal.yaml +13 -0
- package/factory.ts +79 -0
- package/index.ts +79 -0
- package/monitors/dev-portal/auth.ts +44 -0
- package/monitors/dev-portal/availability.ts +44 -0
- package/monitors/dev-portal/errors.ts +47 -0
- package/monitors/dev-portal/gateway.ts +47 -0
- package/monitors/dev-portal/index.ts +6 -0
- package/monitors/dev-portal/latency.ts +44 -0
- package/monitors/dev-portal/logs.ts +26 -0
- package/monitors/index.ts +8 -0
- package/package.json +20 -0
- package/tsconfig.json +16 -0
- package/types.ts +129 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as datadog from '@pulumi/datadog';
|
|
2
|
+
import {
|
|
3
|
+
DashboardDefinition,
|
|
4
|
+
DashboardWidgetDefinition,
|
|
5
|
+
GroupWidgetDefinition,
|
|
6
|
+
PresetDefinition,
|
|
7
|
+
} from './dashboard-types';
|
|
8
|
+
import {settings} from './config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default service definitions for universe presets.
|
|
12
|
+
* Each entry maps a display name to a service tag filter.
|
|
13
|
+
* Add new services here — they'll automatically appear on all dashboards
|
|
14
|
+
* that use getDefaultServicePresets().
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_SERVICES: Array<{name: string; filter: string}> = [
|
|
17
|
+
{name: 'Dev Portal', filter: '*dev-portal*'},
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the default set of saved view presets for all universe services.
|
|
22
|
+
* Includes an "All" option plus one preset per service.
|
|
23
|
+
*
|
|
24
|
+
* @param variableName - The template variable name to bind presets to (e.g., 'service', 'cluster')
|
|
25
|
+
* @param opts.filterPrefix - Optional prefix prepended to each service filter
|
|
26
|
+
* @param opts.extra - Optional additional presets appended after the defaults
|
|
27
|
+
*/
|
|
28
|
+
export function getDefaultServicePresets(
|
|
29
|
+
variableName: string,
|
|
30
|
+
opts?: {filterPrefix?: string; extra?: PresetDefinition[]}
|
|
31
|
+
): PresetDefinition[] {
|
|
32
|
+
const prefix = opts?.filterPrefix || '';
|
|
33
|
+
const presets: PresetDefinition[] = [
|
|
34
|
+
{
|
|
35
|
+
name: 'All',
|
|
36
|
+
templateVariables: [{name: variableName, values: ['*']}],
|
|
37
|
+
},
|
|
38
|
+
...DEFAULT_SERVICES.map(svc => ({
|
|
39
|
+
name: svc.name,
|
|
40
|
+
templateVariables: [
|
|
41
|
+
{name: variableName, values: [`${prefix}${svc.filter}`]},
|
|
42
|
+
],
|
|
43
|
+
})),
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
if (opts?.extra) {
|
|
47
|
+
presets.push(...opts.extra);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return presets;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build standard dashboard tags
|
|
55
|
+
* Note: Datadog dashboards only support tags with the `team:` prefix
|
|
56
|
+
*/
|
|
57
|
+
export function buildDashboardTags(opts: {
|
|
58
|
+
team: string;
|
|
59
|
+
additionalTags?: string[];
|
|
60
|
+
}): string[] {
|
|
61
|
+
const tags = [`team:${opts.team}`];
|
|
62
|
+
|
|
63
|
+
if (opts.additionalTags) {
|
|
64
|
+
tags.push(...opts.additionalTags.filter(t => t.startsWith('team:')));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return tags;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Transform widget definition recursively to convert layout to widgetLayout
|
|
72
|
+
* for nested groupDefinition widgets
|
|
73
|
+
*/
|
|
74
|
+
function transformWidgetDefinition(
|
|
75
|
+
definition: Partial<DashboardWidgetDefinition>
|
|
76
|
+
): Partial<DashboardWidgetDefinition> {
|
|
77
|
+
if (
|
|
78
|
+
definition.groupDefinition &&
|
|
79
|
+
Array.isArray(definition.groupDefinition.widgets)
|
|
80
|
+
) {
|
|
81
|
+
return {
|
|
82
|
+
...definition,
|
|
83
|
+
groupDefinition: {
|
|
84
|
+
...definition.groupDefinition,
|
|
85
|
+
widgets: definition.groupDefinition.widgets.map(
|
|
86
|
+
(widget: GroupWidgetDefinition) => {
|
|
87
|
+
const {layout, ...rest} = widget;
|
|
88
|
+
return {
|
|
89
|
+
widgetLayout: layout
|
|
90
|
+
? {
|
|
91
|
+
x: layout.x,
|
|
92
|
+
y: layout.y,
|
|
93
|
+
width: layout.width,
|
|
94
|
+
height: layout.height,
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
97
|
+
...transformWidgetDefinition(rest),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return definition;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a Datadog dashboard from a DashboardDefinition
|
|
110
|
+
*/
|
|
111
|
+
export function createDashboard(def: DashboardDefinition): datadog.Dashboard {
|
|
112
|
+
const resourceName = `${def.team}-${settings.environment}-${def.id}`;
|
|
113
|
+
|
|
114
|
+
const tags = buildDashboardTags({
|
|
115
|
+
team: def.team,
|
|
116
|
+
additionalTags: def.additionalTags,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return new datadog.Dashboard(resourceName, {
|
|
120
|
+
title: def.title,
|
|
121
|
+
description: def.description,
|
|
122
|
+
layoutType: def.layoutType,
|
|
123
|
+
reflowType: def.reflowType,
|
|
124
|
+
tags: tags,
|
|
125
|
+
|
|
126
|
+
templateVariables: def.templateVariables.map(tv => ({
|
|
127
|
+
name: tv.name,
|
|
128
|
+
prefix: tv.prefix,
|
|
129
|
+
defaults: tv.defaults,
|
|
130
|
+
availableValues: tv.availableValues,
|
|
131
|
+
})),
|
|
132
|
+
|
|
133
|
+
templateVariablePresets: def.presets?.map(preset => ({
|
|
134
|
+
name: preset.name,
|
|
135
|
+
templateVariables: preset.templateVariables.map(tv => ({
|
|
136
|
+
name: tv.name,
|
|
137
|
+
value: tv.value,
|
|
138
|
+
values: tv.values,
|
|
139
|
+
})),
|
|
140
|
+
})),
|
|
141
|
+
|
|
142
|
+
widgets: def.widgets.map(w => ({
|
|
143
|
+
widgetLayout: w.layout
|
|
144
|
+
? {
|
|
145
|
+
x: w.layout.x,
|
|
146
|
+
y: w.layout.y,
|
|
147
|
+
width: w.layout.width,
|
|
148
|
+
height: w.layout.height,
|
|
149
|
+
}
|
|
150
|
+
: undefined,
|
|
151
|
+
...transformWidgetDefinition(w.definition),
|
|
152
|
+
})),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Create multiple dashboards from an array of definitions
|
|
158
|
+
*/
|
|
159
|
+
export function createDashboards(
|
|
160
|
+
defs: DashboardDefinition[]
|
|
161
|
+
): Record<string, datadog.Dashboard> {
|
|
162
|
+
const dashboards: Record<string, datadog.Dashboard> = {};
|
|
163
|
+
|
|
164
|
+
for (const def of defs) {
|
|
165
|
+
dashboards[def.id] = createDashboard(def);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return dashboards;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Create a Datadog dashboard from raw JSON export.
|
|
173
|
+
*
|
|
174
|
+
* Use this when importing a dashboard directly from the Datadog UI JSON export.
|
|
175
|
+
* The JSON is passed through as-is via the DashboardJson resource.
|
|
176
|
+
*/
|
|
177
|
+
export function createDashboardFromJson(opts: {
|
|
178
|
+
id: string;
|
|
179
|
+
team: string;
|
|
180
|
+
dashboardJson: object;
|
|
181
|
+
}): datadog.DashboardJson {
|
|
182
|
+
const resourceName = `${opts.team}-${settings.environment}-${opts.id}`;
|
|
183
|
+
|
|
184
|
+
return new datadog.DashboardJson(resourceName, {
|
|
185
|
+
dashboard: JSON.stringify(opts.dashboardJson),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as datadog from '@pulumi/datadog';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Template variable definition for dashboard filters
|
|
5
|
+
*/
|
|
6
|
+
export interface TemplateVariableDefinition {
|
|
7
|
+
/** Variable name (used in queries as $name) */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Tag prefix to filter by (e.g., 'unienv' for unienv:* tags) */
|
|
10
|
+
prefix: string;
|
|
11
|
+
/** Default value(s) - ['*'] for all */
|
|
12
|
+
defaults: string[];
|
|
13
|
+
/** Available values to show in dropdown (empty = auto-discover) */
|
|
14
|
+
availableValues?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Template variable preset for quick filtering
|
|
19
|
+
*/
|
|
20
|
+
export interface PresetDefinition {
|
|
21
|
+
/** Preset display name */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Template variable values for this preset */
|
|
24
|
+
templateVariables: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
/** Single value (deprecated - use values instead) */
|
|
27
|
+
value?: string;
|
|
28
|
+
/** Multiple values (preferred) */
|
|
29
|
+
values?: string[];
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Widget layout position for fixed reflow dashboards
|
|
35
|
+
*/
|
|
36
|
+
export interface WidgetLayout {
|
|
37
|
+
/** X position (horizontal) */
|
|
38
|
+
x: number;
|
|
39
|
+
/** Y position (vertical) */
|
|
40
|
+
y: number;
|
|
41
|
+
/** Width in grid units */
|
|
42
|
+
width: number;
|
|
43
|
+
/** Height in grid units */
|
|
44
|
+
height: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Widget definition that uses 'layout' instead of 'widgetLayout' for consistency.
|
|
49
|
+
* This type is used for widgets inside groupDefinition.
|
|
50
|
+
*/
|
|
51
|
+
export type GroupWidgetDefinition = Omit<
|
|
52
|
+
datadog.types.input.DashboardWidgetGroupDefinitionWidget,
|
|
53
|
+
'widgetLayout'
|
|
54
|
+
> & {
|
|
55
|
+
layout?: WidgetLayout;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extended DashboardWidget that supports 'layout' for nested group widgets
|
|
60
|
+
*/
|
|
61
|
+
export type DashboardWidgetDefinition = Omit<
|
|
62
|
+
datadog.types.input.DashboardWidget,
|
|
63
|
+
'groupDefinition'
|
|
64
|
+
> & {
|
|
65
|
+
groupDefinition?: Omit<
|
|
66
|
+
datadog.types.input.DashboardWidgetGroupDefinition,
|
|
67
|
+
'widgets'
|
|
68
|
+
> & {
|
|
69
|
+
widgets?: GroupWidgetDefinition[];
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Dashboard definition - simplified interface for defining dashboards
|
|
75
|
+
*
|
|
76
|
+
* This mirrors the MonitorDefinition pattern for consistency.
|
|
77
|
+
*/
|
|
78
|
+
export interface DashboardDefinition {
|
|
79
|
+
/** Unique identifier (used in resource name, e.g., 'dev-portal_overview') */
|
|
80
|
+
id: string;
|
|
81
|
+
|
|
82
|
+
/** Dashboard title */
|
|
83
|
+
title: string;
|
|
84
|
+
|
|
85
|
+
/** Dashboard description */
|
|
86
|
+
description?: string;
|
|
87
|
+
|
|
88
|
+
/** Owning team (used for tagging and resource naming) */
|
|
89
|
+
team: string;
|
|
90
|
+
|
|
91
|
+
/** Layout type - 'ordered' for grid layout, 'free' for absolute positioning */
|
|
92
|
+
layoutType: 'ordered' | 'free';
|
|
93
|
+
|
|
94
|
+
/** Reflow type for ordered layouts - 'fixed' requires layouts, 'auto' does not */
|
|
95
|
+
reflowType?: 'auto' | 'fixed';
|
|
96
|
+
|
|
97
|
+
/** Template variables for filtering */
|
|
98
|
+
templateVariables: TemplateVariableDefinition[];
|
|
99
|
+
|
|
100
|
+
/** Presets for quick filtering */
|
|
101
|
+
presets?: PresetDefinition[];
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Widget definitions using Pulumi Dashboard widget format.
|
|
105
|
+
* Each widget should have exactly one definition type
|
|
106
|
+
* (e.g., listStreamDefinition, manageStatusDefinition, etc.)
|
|
107
|
+
*
|
|
108
|
+
* For groupDefinition widgets, use 'layout' instead of 'widgetLayout'
|
|
109
|
+
* for nested widgets. The factory will transform this to the correct format.
|
|
110
|
+
*/
|
|
111
|
+
widgets: Array<{
|
|
112
|
+
/** Layout position (required for 'fixed' reflow type) */
|
|
113
|
+
layout?: WidgetLayout;
|
|
114
|
+
/**
|
|
115
|
+
* Widget definition - should contain exactly one widget type property.
|
|
116
|
+
* Examples:
|
|
117
|
+
* - { listStreamDefinition: {...} }
|
|
118
|
+
* - { manageStatusDefinition: {...} }
|
|
119
|
+
* - { timeseriesDefinition: {...} }
|
|
120
|
+
* - { groupDefinition: { widgets: [{ layout: {...}, noteDefinition: {...} }] } }
|
|
121
|
+
*/
|
|
122
|
+
definition: Partial<DashboardWidgetDefinition>;
|
|
123
|
+
}>;
|
|
124
|
+
|
|
125
|
+
/** Additional tags beyond the standard ones (team, env, managed-by) */
|
|
126
|
+
additionalTags?: string[];
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {devPortalDashboards} from './service-dashboard';
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {DashboardDefinition} from '../../dashboard-types';
|
|
2
|
+
import {getDefaultServicePresets} from '../../dashboard-factory';
|
|
3
|
+
import {settings} from '../../config';
|
|
4
|
+
|
|
5
|
+
// ALB metrics — these have data immediately via AWS integration (no APM required)
|
|
6
|
+
const albFilter = `name:dev-portal-lb,unienv:${settings.environment}`;
|
|
7
|
+
|
|
8
|
+
export const devPortalDashboards: DashboardDefinition[] = [
|
|
9
|
+
{
|
|
10
|
+
id: 'dev-portal_service_overview',
|
|
11
|
+
title: `[Dev Portal] [${settings.environment}] Service Overview`,
|
|
12
|
+
description:
|
|
13
|
+
'Dev Portal service health: request rate, errors, latency, and status codes (ALB metrics).',
|
|
14
|
+
team: 'dev-portal',
|
|
15
|
+
layoutType: 'ordered',
|
|
16
|
+
reflowType: 'fixed',
|
|
17
|
+
templateVariables: [
|
|
18
|
+
{
|
|
19
|
+
name: 'env',
|
|
20
|
+
prefix: 'unienv',
|
|
21
|
+
defaults: [settings.environment],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
presets: getDefaultServicePresets('service'),
|
|
25
|
+
widgets: [
|
|
26
|
+
// Row 1: Request Rate + Error Rate
|
|
27
|
+
{
|
|
28
|
+
layout: {x: 0, y: 0, width: 6, height: 4},
|
|
29
|
+
definition: {
|
|
30
|
+
timeseriesDefinition: {
|
|
31
|
+
title: 'Request Rate',
|
|
32
|
+
showLegend: true,
|
|
33
|
+
requests: [
|
|
34
|
+
{
|
|
35
|
+
formulas: [{formulaExpression: 'query1'}],
|
|
36
|
+
queries: [
|
|
37
|
+
{
|
|
38
|
+
metricQuery: {
|
|
39
|
+
dataSource: 'metrics',
|
|
40
|
+
name: 'query1',
|
|
41
|
+
query: `sum:aws.applicationelb.request_count{${albFilter}}.as_rate()`,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
style: {palette: 'dog_classic', lineType: 'solid', lineWidth: 'normal'},
|
|
46
|
+
displayType: 'line',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
layout: {x: 6, y: 0, width: 6, height: 4},
|
|
54
|
+
definition: {
|
|
55
|
+
timeseriesDefinition: {
|
|
56
|
+
title: 'Error Rate (%)',
|
|
57
|
+
showLegend: true,
|
|
58
|
+
requests: [
|
|
59
|
+
{
|
|
60
|
+
formulas: [{formulaExpression: 'query1 / query2 * 100'}],
|
|
61
|
+
queries: [
|
|
62
|
+
{
|
|
63
|
+
metricQuery: {
|
|
64
|
+
dataSource: 'metrics',
|
|
65
|
+
name: 'query1',
|
|
66
|
+
query: `sum:aws.applicationelb.httpcode_target_5xx{${albFilter}}.as_count()`,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
metricQuery: {
|
|
71
|
+
dataSource: 'metrics',
|
|
72
|
+
name: 'query2',
|
|
73
|
+
query: `sum:aws.applicationelb.request_count{${albFilter}}.as_count()`,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
style: {palette: 'warm', lineType: 'solid', lineWidth: 'normal'},
|
|
78
|
+
displayType: 'line',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
// Row 2: Latency Percentiles + HTTP Status Distribution
|
|
85
|
+
{
|
|
86
|
+
layout: {x: 0, y: 4, width: 6, height: 4},
|
|
87
|
+
definition: {
|
|
88
|
+
timeseriesDefinition: {
|
|
89
|
+
title: 'Latency Percentiles',
|
|
90
|
+
showLegend: true,
|
|
91
|
+
requests: [
|
|
92
|
+
{
|
|
93
|
+
formulas: [
|
|
94
|
+
{formulaExpression: 'query1', alias: 'P95'},
|
|
95
|
+
{formulaExpression: 'query2', alias: 'P99'},
|
|
96
|
+
],
|
|
97
|
+
queries: [
|
|
98
|
+
{
|
|
99
|
+
metricQuery: {
|
|
100
|
+
dataSource: 'metrics',
|
|
101
|
+
name: 'query1',
|
|
102
|
+
query: `avg:aws.applicationelb.target_response_time.p95{${albFilter}}`,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
metricQuery: {
|
|
107
|
+
dataSource: 'metrics',
|
|
108
|
+
name: 'query2',
|
|
109
|
+
query: `avg:aws.applicationelb.target_response_time.p99{${albFilter}}`,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
style: {palette: 'dog_classic', lineType: 'solid', lineWidth: 'normal'},
|
|
114
|
+
displayType: 'line',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
layout: {x: 6, y: 4, width: 6, height: 4},
|
|
122
|
+
definition: {
|
|
123
|
+
toplistDefinition: {
|
|
124
|
+
title: 'HTTP Status Code Distribution',
|
|
125
|
+
requests: [
|
|
126
|
+
{
|
|
127
|
+
formulas: [{formulaExpression: 'query1 + query2 + query3 + query4', limit: {count: 10, order: 'desc'}}],
|
|
128
|
+
queries: [
|
|
129
|
+
{
|
|
130
|
+
metricQuery: {
|
|
131
|
+
dataSource: 'metrics',
|
|
132
|
+
name: 'query1',
|
|
133
|
+
query: `sum:aws.applicationelb.httpcode_target_2xx{${albFilter}}.as_count()`,
|
|
134
|
+
aggregator: 'sum',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
metricQuery: {
|
|
139
|
+
dataSource: 'metrics',
|
|
140
|
+
name: 'query2',
|
|
141
|
+
query: `sum:aws.applicationelb.httpcode_target_3xx{${albFilter}}.as_count()`,
|
|
142
|
+
aggregator: 'sum',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
metricQuery: {
|
|
147
|
+
dataSource: 'metrics',
|
|
148
|
+
name: 'query3',
|
|
149
|
+
query: `sum:aws.applicationelb.httpcode_target_4xx{${albFilter}}.as_count()`,
|
|
150
|
+
aggregator: 'sum',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
metricQuery: {
|
|
155
|
+
dataSource: 'metrics',
|
|
156
|
+
name: 'query4',
|
|
157
|
+
query: `sum:aws.applicationelb.httpcode_target_5xx{${albFilter}}.as_count()`,
|
|
158
|
+
aggregator: 'sum',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {devPortalDashboards} from './dev-portal';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as pulumi from '@pulumi/pulumi';
|
|
2
|
+
import {createDashboards} from './dashboard-factory';
|
|
3
|
+
import {devPortalDashboards} from './definitions';
|
|
4
|
+
import {settings} from './config';
|
|
5
|
+
import {DashboardDefinition} from './dashboard-types';
|
|
6
|
+
|
|
7
|
+
// Log configuration
|
|
8
|
+
pulumi.log.info(`Environment: ${settings.environment}`);
|
|
9
|
+
pulumi.log.info(`Team: ${settings.defaultTeam}`);
|
|
10
|
+
pulumi.log.info(`Tag Filter: ${settings.tagFilter}`);
|
|
11
|
+
|
|
12
|
+
// Team dashboard definitions
|
|
13
|
+
const teamDashboards: Record<string, DashboardDefinition[]> = {
|
|
14
|
+
'dev-portal': devPortalDashboards,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Get dashboards for current team
|
|
18
|
+
const team = settings.defaultTeam;
|
|
19
|
+
const currentTeamDashboards = teamDashboards[team] || [];
|
|
20
|
+
|
|
21
|
+
if (currentTeamDashboards.length === 0) {
|
|
22
|
+
pulumi.log.warn(`No dashboards defined for team: ${team}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create dashboards
|
|
26
|
+
const createdDashboards = createDashboards(currentTeamDashboards);
|
|
27
|
+
const dashboardIdMap: Record<string, pulumi.Output<string>> =
|
|
28
|
+
Object.fromEntries(
|
|
29
|
+
Object.entries(createdDashboards).map(([k, v]) => [k, v.id])
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Exports
|
|
33
|
+
export const dashboardIds = dashboardIdMap;
|
|
34
|
+
|
|
35
|
+
export const summary = {
|
|
36
|
+
team,
|
|
37
|
+
environment: settings.environment,
|
|
38
|
+
totalDashboards: Object.keys(dashboardIdMap).length,
|
|
39
|
+
dashboards: Object.keys(dashboardIdMap),
|
|
40
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "datadog-dashboards-universe",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Datadog dashboards for Universe services",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"preview": "pulumi preview",
|
|
8
|
+
"up": "pulumi up",
|
|
9
|
+
"destroy": "pulumi destroy",
|
|
10
|
+
"refresh": "pulumi refresh"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@pulumi/datadog": "4.60.0",
|
|
14
|
+
"@pulumi/pulumi": "3.207.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "22.13.1",
|
|
18
|
+
"typescript": "5.8.3"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
imports:
|
|
2
|
+
- shared-infra/datadog-2026
|
|
3
|
+
- shared-infra/incident
|
|
4
|
+
|
|
5
|
+
values:
|
|
6
|
+
pulumiConfig:
|
|
7
|
+
datadog:apiKey: ${pulumiConfig["env:DD_API_KEY"]}
|
|
8
|
+
datadog:appKey: ${pulumiConfig["env:DD_APP_KEY"]}
|
|
9
|
+
datadog-cloud-universe:teams: ${teams}
|
|
10
|
+
datadog-cloud-universe:tagFilterExtra: "service:dev-portal*"
|
|
11
|
+
datadog-cloud-universe:defaultTeam: dev-portal
|
|
12
|
+
datadog-cloud-universe:disablePaging: 'true'
|
|
13
|
+
datadog-cloud-universe:disableSlack: 'true'
|
package/factory.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as datadog from '@pulumi/datadog';
|
|
2
|
+
import {MonitorDefinition, defaultMonitorOptions} from './types';
|
|
3
|
+
import {buildTags, buildMessage, settings} from './config';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a Datadog monitor from a MonitorDefinition
|
|
7
|
+
*/
|
|
8
|
+
export function createMonitor(def: MonitorDefinition): datadog.Monitor {
|
|
9
|
+
const resourceName = `${def.team}-${settings.environment}-${def.id}`;
|
|
10
|
+
|
|
11
|
+
// Format team name for display: sre -> SRE, dev-portal -> Dev-portal
|
|
12
|
+
const teamDisplay =
|
|
13
|
+
def.team.toUpperCase() === def.team
|
|
14
|
+
? def.team
|
|
15
|
+
: def.team.charAt(0).toUpperCase() + def.team.slice(1);
|
|
16
|
+
|
|
17
|
+
// Auto-prepend [TEAM] to monitor name if not already present
|
|
18
|
+
const monitorName = def.name.startsWith('[')
|
|
19
|
+
? def.name
|
|
20
|
+
: `[${teamDisplay}] ${def.name}`;
|
|
21
|
+
|
|
22
|
+
const tags = buildTags({
|
|
23
|
+
signalId: def.id,
|
|
24
|
+
team: def.team,
|
|
25
|
+
enablePaging: def.enablePaging,
|
|
26
|
+
additionalTags: def.additionalTags,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const message = buildMessage({
|
|
30
|
+
alertBody: def.alertBody,
|
|
31
|
+
recoveryBody: def.recoveryBody,
|
|
32
|
+
team: def.team,
|
|
33
|
+
logQuery: def.logQuery,
|
|
34
|
+
runbookUrl: def.runbookUrl,
|
|
35
|
+
readmeUrl: def.readmeUrl,
|
|
36
|
+
dashboards: def.dashboards,
|
|
37
|
+
includeIncidentWebhook: def.includeIncidentWebhook,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return new datadog.Monitor(resourceName, {
|
|
41
|
+
...defaultMonitorOptions,
|
|
42
|
+
name: monitorName,
|
|
43
|
+
type: def.type,
|
|
44
|
+
query: def.query,
|
|
45
|
+
message: message,
|
|
46
|
+
tags: tags,
|
|
47
|
+
priority: def.priority.toString(),
|
|
48
|
+
monitorThresholds: def.thresholds
|
|
49
|
+
? {
|
|
50
|
+
critical: def.thresholds.critical?.toString(),
|
|
51
|
+
warning: def.thresholds.warning?.toString(),
|
|
52
|
+
criticalRecovery: def.thresholds.criticalRecovery?.toString(),
|
|
53
|
+
warningRecovery: def.thresholds.warningRecovery?.toString(),
|
|
54
|
+
}
|
|
55
|
+
: undefined,
|
|
56
|
+
monitorThresholdWindows: def.thresholdWindows,
|
|
57
|
+
noDataTimeframe: def.noDataTimeframe,
|
|
58
|
+
notifyNoData: def.notifyNoData,
|
|
59
|
+
renotifyInterval: def.renotifyInterval,
|
|
60
|
+
newGroupDelay: def.newGroupDelay,
|
|
61
|
+
evaluationDelay: def.evaluationDelay,
|
|
62
|
+
onMissingData: def.onMissingData,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create multiple monitors from an array of definitions
|
|
68
|
+
*/
|
|
69
|
+
export function createMonitors(
|
|
70
|
+
defs: MonitorDefinition[]
|
|
71
|
+
): Record<string, datadog.Monitor> {
|
|
72
|
+
const monitors: Record<string, datadog.Monitor> = {};
|
|
73
|
+
|
|
74
|
+
for (const def of defs) {
|
|
75
|
+
monitors[def.id] = createMonitor(def);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return monitors;
|
|
79
|
+
}
|