@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.
@@ -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,7 @@
1
+ imports:
2
+ - shared-infra/datadog-2026
3
+
4
+ values:
5
+ pulumiConfig:
6
+ datadog:apiKey: ${pulumiConfig["env:DD_API_KEY"]}
7
+ datadog:appKey: ${pulumiConfig["env:DD_APP_KEY"]}
@@ -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
+ }