Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c9e83f6
feat: create grafana component
bornast Mar 16, 2026
1df2135
docs: add grafana aws account hardcoded value comment
bornast Mar 16, 2026
ff2d228
refactor: provider config value extraction
bornast Mar 17, 2026
02cb734
feat: remove tags prop from grafana builder and component
bornast Mar 17, 2026
452c85c
refactor: rename grafana resources
bornast Mar 18, 2026
fd465d2
feat: grafana addDashboard builder method
bornast Mar 18, 2026
37e5dde
refactor: config naming
bornast Mar 18, 2026
60d26c2
Merge branch 'feat/grafana-comp' into feat/grafana-dashboards
bornast Mar 18, 2026
509db81
feat: add name public prop to grafana component
bornast Mar 18, 2026
fd8dd9d
Merge branch 'feat/grafana-comp' into feat/grafana-dashboards
bornast Mar 18, 2026
29e7167
feat: introduce grafana connections
bornast Mar 19, 2026
9aced94
refactor: remove unnecessary lines
bornast Mar 19, 2026
b766efe
refactor: method signatures
bornast Mar 19, 2026
99376d5
feat: make grafana props readonly
bornast Mar 19, 2026
50398f2
Merge branch 'feat/grafana-comp' into feat/grafana-dashboards
bornast Mar 19, 2026
631ded8
feat: add name prop to grafana component
bornast Mar 19, 2026
8cec815
refactor: method signatures
bornast Mar 25, 2026
1626551
refactor: method signatures
bornast Mar 25, 2026
075cad6
Merge branch 'feat/grafana-comp' into feat/grafana-dashboards
bornast Mar 26, 2026
fa0dd95
feat: generic dashboard builder
bornast Mar 27, 2026
d07403c
Merge branch 'master' into feat/grafana-dashboards
bornast Mar 27, 2026
6a58b75
Merge branch 'master' into feat/grafana-dashboards
bornast Mar 30, 2026
fde5f48
refactor: dashboard build configuration
bornast Mar 30, 2026
21a9b46
refactor: dashboard builder
bornast Mar 30, 2026
4d46c9b
feat: add dashboard builder default configuration
bornast Mar 30, 2026
813775e
refactor: panel export type
bornast Mar 30, 2026
a389874
refactor: panel types
bornast Mar 30, 2026
0c6d123
refactor: rename prometheusNamespace to ampNamespace
bornast Mar 30, 2026
e522d6f
refactor: panel types
bornast Mar 30, 2026
36dac57
Merge branch 'master' into feat/grafana-dashboards
bornast Mar 30, 2026
3049a67
refactor: error msg
bornast Mar 30, 2026
518e77c
refactor: rename connection and dashboard creation methods
bornast Mar 31, 2026
7f56ddf
Merge branch 'master' into feat/grafana-dashboards
bornast Mar 31, 2026
5598b23
feat: add grafana folderName parameter
bornast Mar 31, 2026
75a00ff
feat: make amp plugin installation optional
bornast Mar 31, 2026
ec0eddf
feat: make xray connection plugin installation optional
bornast Mar 31, 2026
5b5c231
feat: add dataSourceName prop
bornast Mar 31, 2026
46e116c
feat: add slo dashboard builder method
bornast Mar 31, 2026
f899b71
refactor: data source prop type
bornast Mar 31, 2026
a94815a
refactor: grafana dashboard name
bornast Mar 31, 2026
3a98fe0
refactor: rename slo dashboard
bornast Mar 31, 2026
9bf81b4
refactor: grafana builder error messages
bornast Mar 31, 2026
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
39 changes: 36 additions & 3 deletions src/components/grafana/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,27 @@ import {
XRayConnection,
} from './connections';
import { Grafana } from './grafana';
import type { GrafanaDashboardBuilder } from './dashboards/builder';
import { createSloDashboard, SloDashboard } from './dashboards/slo';

export class GrafanaBuilder {
private readonly name: string;
private readonly connectionBuilders: GrafanaConnection.ConnectionBuilder[] =
private readonly connectionBuilders: GrafanaConnection.CreateConnection[] =
[];
private readonly dashboardBuilders: GrafanaDashboardBuilder.CreateDashboard[] =
[];
private folderName?: string;

constructor(name: string) {
this.name = name;
}

public withFolderName(folderName: string): this {
this.folderName = folderName;

return this;
}

public addAmp(name: string, args: AMPConnection.Args): this {
this.connectionBuilders.push(opts => new AMPConnection(name, args, opts));

Expand All @@ -39,23 +50,45 @@ export class GrafanaBuilder {
return this;
}

public addConnection(builder: GrafanaConnection.ConnectionBuilder): this {
public addConnection(builder: GrafanaConnection.CreateConnection): this {
this.connectionBuilders.push(builder);

return this;
}

public addSloDashboard(config: SloDashboard.Args): this {
this.dashboardBuilders.push(createSloDashboard(config));

return this;
}

public addDashboard(
dashboard: GrafanaDashboardBuilder.CreateDashboard,
): this {
this.dashboardBuilders.push(dashboard);

return this;
}

public build(opts: pulumi.ComponentResourceOptions = {}): Grafana {
if (!this.connectionBuilders.length) {
throw new Error(
'At least one connection is required. Call addConnection() to add custom connection or use one of existing connection builders.',
'At least one connection is required. Call addConnection() to add a custom connection or use one of the existing connection builders.',
);
}

if (!this.dashboardBuilders.length) {
throw new Error(
'At least one dashboard is required. Call addDashboard() to add a custom dashboard or use one of the existing dashboard builders.',
);
}

return new Grafana(
this.name,
{
connectionBuilders: this.connectionBuilders,
dashboardBuilders: this.dashboardBuilders,
folderName: this.folderName,
},
opts,
);
Expand Down
20 changes: 12 additions & 8 deletions src/components/grafana/connections/amp-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ export namespace AMPConnection {
endpoint: pulumi.Input<string>;
region?: string;
pluginVersion?: string;
installPlugin?: boolean;
};
}

const defaults = {
pluginVersion: 'latest',
region: awsConfig.require('region'),
pluginVersion: 'latest',
installPlugin: true,
};

export class AMPConnection extends GrafanaConnection {
public readonly name: string;
public readonly dataSource: grafana.oss.DataSource;
public readonly plugin: grafana.cloud.PluginInstallation;
public readonly rolePolicy: aws.iam.RolePolicy;
public readonly plugin?: grafana.cloud.PluginInstallation;

constructor(
name: string,
Expand All @@ -38,7 +40,11 @@ export class AMPConnection extends GrafanaConnection {
this.name = name;

this.rolePolicy = this.createRolePolicy();
this.plugin = this.createPlugin(argsWithDefaults.pluginVersion);

if (argsWithDefaults.installPlugin) {
this.plugin = this.createPlugin(argsWithDefaults.pluginVersion);
}

this.dataSource = this.createDataSource(
argsWithDefaults.region,
argsWithDefaults.endpoint,
Expand Down Expand Up @@ -91,12 +97,10 @@ export class AMPConnection extends GrafanaConnection {
region: string,
endpoint: AMPConnection.Args['endpoint'],
): grafana.oss.DataSource {
const dataSourceName = `${this.name}-amp-datasource`;

return new grafana.oss.DataSource(
dataSourceName,
`${this.name}-amp-datasource`,
{
name: dataSourceName,
name: this.dataSourceName,
type: pluginName,
url: endpoint,
jsonDataEncoded: pulumi.jsonStringify({
Expand All @@ -106,7 +110,7 @@ export class AMPConnection extends GrafanaConnection {
sigV4AssumeRoleArn: this.role.arn,
}),
},
{ dependsOn: [this.plugin], parent: this },
{ dependsOn: this.plugin ? [this.plugin] : [], parent: this },
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ export class CloudWatchLogsConnection extends GrafanaConnection {
}

private createDataSource(region: string): grafana.oss.DataSource {
const dataSourceName = `${this.name}-cloudwatch-logs-datasource`;

return new grafana.oss.DataSource(
dataSourceName,
`${this.name}-cloudwatch-logs-datasource`,
{
name: dataSourceName,
name: this.dataSourceName,
type: 'cloudwatch',
jsonDataEncoded: pulumi.jsonStringify({
authType: 'grafana_assume_role',
Expand Down
6 changes: 5 additions & 1 deletion src/components/grafana/connections/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const grafanaConfig = new pulumi.Config('grafana');
export namespace GrafanaConnection {
export type Args = {
awsAccountId: string;
dataSourceName?: string;
};

export type ConnectionBuilder = (
export type CreateConnection = (
opts: pulumi.ComponentResourceOptions,
) => GrafanaConnection;
}
Expand All @@ -19,6 +20,7 @@ export abstract class GrafanaConnection extends pulumi.ComponentResource {
public readonly name: string;
public readonly role: aws.iam.Role;
public abstract readonly dataSource: grafana.oss.DataSource;
protected readonly dataSourceName: string;

constructor(
type: string,
Expand All @@ -30,6 +32,8 @@ export abstract class GrafanaConnection extends pulumi.ComponentResource {

this.name = name;

this.dataSourceName = args.dataSourceName ?? `${name}-datasource`;

this.role = this.createIamRole(args.awsAccountId);

this.registerOutputs();
Expand Down
20 changes: 12 additions & 8 deletions src/components/grafana/connections/xray-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,21 @@ export namespace XRayConnection {
export type Args = GrafanaConnection.Args & {
region?: string;
pluginVersion?: string;
installPlugin?: boolean;
};
}

const defaults = {
pluginVersion: 'latest',
region: awsConfig.require('region'),
pluginVersion: 'latest',
installPlugin: true,
};

export class XRayConnection extends GrafanaConnection {
public readonly name: string;
public readonly dataSource: grafana.oss.DataSource;
public readonly plugin: grafana.cloud.PluginInstallation;
public readonly rolePolicy: aws.iam.RolePolicy;
public readonly plugin?: grafana.cloud.PluginInstallation;

constructor(
name: string,
Expand All @@ -37,7 +39,11 @@ export class XRayConnection extends GrafanaConnection {
this.name = name;

this.rolePolicy = this.createRolePolicy();
this.plugin = this.createPlugin(argsWithDefaults.pluginVersion);

if (argsWithDefaults.installPlugin) {
this.plugin = this.createPlugin(argsWithDefaults.pluginVersion);
}

this.dataSource = this.createDataSource(argsWithDefaults.region);

this.registerOutputs();
Expand Down Expand Up @@ -89,20 +95,18 @@ export class XRayConnection extends GrafanaConnection {
}

private createDataSource(region: string): grafana.oss.DataSource {
const dataSourceName = `${this.name}-x-ray-datasource`;

return new grafana.oss.DataSource(
dataSourceName,
`${this.name}-x-ray-datasource`,
{
name: dataSourceName,
name: this.dataSourceName,
type: pluginName,
jsonDataEncoded: pulumi.jsonStringify({
authType: 'grafana_assume_role',
assumeRoleArn: this.role.arn,
defaultRegion: region,
}),
},
{ dependsOn: [this.plugin], parent: this },
{ dependsOn: this.plugin ? [this.plugin] : [], parent: this },
);
}
}
72 changes: 72 additions & 0 deletions src/components/grafana/dashboards/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import * as pulumi from '@pulumi/pulumi';
import * as grafana from '@pulumiverse/grafana';
import { Panel } from '../panels/types';
import { mergeWithDefaults } from '../../../shared/merge-with-defaults';

export namespace GrafanaDashboardBuilder {
export type Config = {
timezone?: string;
refresh?: string;
};

export type CreateDashboard = (
folder?: grafana.oss.Folder,
opts?: pulumi.ComponentResourceOptions,
) => grafana.oss.Dashboard;
}

const defaults = {
timezone: 'browser',
refresh: '10s',
};

export class GrafanaDashboardBuilder {
private readonly name: string;
private readonly title: string;
private readonly panels: Panel[] = [];
private configuration: GrafanaDashboardBuilder.Config = {};

constructor(name: string, title: string) {
this.name = name;
this.title = title;
}

withConfig(options: GrafanaDashboardBuilder.Config): this {
this.configuration = options;

return this;
}

addPanel(panel: Panel): this {
this.panels.push(panel);

return this;
}

build(): GrafanaDashboardBuilder.CreateDashboard {
if (!this.panels.length) {
throw new Error(
'At least one panel is required. Call addPanel() to add a panel.',
);
}

const { name, title, panels } = this;
const options = mergeWithDefaults(defaults, this.configuration);

return (folder, opts) => {
return new grafana.oss.Dashboard(
`${name}-dashboard`,
{
folder: folder?.uid,
configJson: pulumi.jsonStringify({
title,
timezone: options.timezone,
refresh: options.refresh,
panels,
}),
},
opts,
);
};
}
}
4 changes: 2 additions & 2 deletions src/components/grafana/dashboards/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { default as WebServerSloDashboardBuilder } from './web-server-slo';
export * as panel from './panels';
export { GrafanaDashboardBuilder as DashboardBuilder } from './builder';
export { createSloDashboard } from './slo';
56 changes: 56 additions & 0 deletions src/components/grafana/dashboards/slo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { mergeWithDefaults } from '../../../shared/merge-with-defaults';
import { GrafanaDashboardBuilder } from './builder';
import { queries as promQ } from '../../prometheus';
import {
createAvailabilityPanel,
createAvailabilityBurnRatePanel,
} from '../panels/availability';
import {
createSuccessRatePanel,
createSuccessRateTimeSeriesPanel,
createSuccessRateBurnRatePanel,
} from '../panels/success-rate';
import {
createLatencyPanel,
createLatencyPercentilePanel,
createLatencyPercentagePanel,
createLatencyBurnRatePanel,
} from '../panels/latency';

export namespace SloDashboard {
export type Args = {
name: string;
title: string;
ampNamespace: string;
filter: string;
dataSourceName: string;
target?: number;
window?: promQ.TimeRange;
shortWindow?: promQ.TimeRange;
targetLatency?: number;
};
}

const defaults = {
target: 0.99,
window: '30d',
shortWindow: '5m',
targetLatency: 250,
};

export function createSloDashboard(
config: SloDashboard.Args,
): GrafanaDashboardBuilder.CreateDashboard {
const argsWithDefaults = mergeWithDefaults(defaults, config);
return new GrafanaDashboardBuilder(config.name, argsWithDefaults.title)
.addPanel(createAvailabilityPanel(argsWithDefaults))
.addPanel(createAvailabilityBurnRatePanel(argsWithDefaults))
.addPanel(createSuccessRatePanel(argsWithDefaults))
.addPanel(createSuccessRateTimeSeriesPanel(argsWithDefaults))
.addPanel(createSuccessRateBurnRatePanel(argsWithDefaults))
.addPanel(createLatencyPanel(argsWithDefaults))
.addPanel(createLatencyPercentilePanel(argsWithDefaults))
.addPanel(createLatencyPercentagePanel(argsWithDefaults))
.addPanel(createLatencyBurnRatePanel(argsWithDefaults))
.build();
}
Loading