Skip to content

Commit

Permalink
feat: initial grafana API (#2)
Browse files Browse the repository at this point in the history
I used cdk8s to import Grafana custom resource definitions from the Grafana operator repository (here: https://github.com/grafana-operator/grafana-operator/tree/v3.10.2/deploy/crds), but some of them had to be manually modified due to typos and/or missing fields.

I tested the generated manifests on my local machine using the kubernetes tool "kind" ("minikube" works just as well). The process was:
- generate a manifest (by creating a chart and using the API)
- create a kind cluster
- install the grafana operator on a specific namespace (e.g. "grafana")
- apply the manifest in that namespace
- `kubectl port-forward service/grafana-service -n <namespace> :3000`
- check that Grafana is working in browser

TODO:
- [x] update based on README
- [x] add more tests
  • Loading branch information
Chriscbr authored Jul 22, 2021
1 parent df2c3ab commit fd26bcd
Show file tree
Hide file tree
Showing 13 changed files with 21,758 additions and 31 deletions.
471 changes: 464 additions & 7 deletions API.md

Large diffs are not rendered by default.

30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,6 @@ const dashboard = grafana.addDashboard('my-dashboard', {
title: 'My Dashboard',
refreshRate: Duration.seconds(10),
timeRange: Duration.hours(6), // show metrics from now-6h to now
panels: [
{
"type": "text",
"title": "Panel Title",
"gridPos": {
"x": 0,
"y": 0,
"w": 12,
"h": 9
},
"mode": "markdown",
"content": "# title"
},
],
plugins: [
{
name: 'grafana-piechart-panel',
Expand All @@ -59,6 +45,22 @@ const dashboard = grafana.addDashboard('my-dashboard', {
});
```

Note: the kubernetes grafana operator only supports one Grafana instance per
namespace (see https://github.com/grafana-operator/grafana-operator/issues/174).
This may require specifying namespaces explicitly, e.g.:

```typescript
const devGrafana = new Grafana(this, 'my-grafana', {
namespace: 'dev',
});
const prodGrafana = new Grafana(this, 'my-grafana', {
namespace: 'prod',
});
```

The grafana operator must be installed in each namespace for the resources in
that namespace to be recognized.

## Security

See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more
Expand Down
161 changes: 161 additions & 0 deletions src/dashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Duration } from 'cdk8s';
import { Construct } from 'constructs';
import { GrafanaDashboard } from './imports/grafana-dashboard';

export interface DashboardProps {
/**
* Title of the dashboard.
*/
readonly title: string;

/**
* Group dashboards into folders.
* @default - default folder
*/
readonly folder?: string;

/**
* Specify a mapping from data source variables to data source names.
* This is only needed if you are importing an existing dashboard's JSON
* and it specifies variables within an "__inputs" field.
*
* @example { DS_PROMETHEUS: "my-prometheus-ds" }
* @default - no data source variables
*/
readonly dataSourceVariables?: { [name: string]: string };

/**
* Auto-refresh interval.
* @default - 5 seconds
*/
readonly refreshRate?: Duration;

/**
* Time range for the dashboard, e.g. last 6 hours, last 7 days, etc.
* @default - 6 hours
*/
readonly timeRange?: Duration;

/**
* Specify plugins required by the dashboard.
*/
readonly plugins?: GrafanaPlugin[];

/**
* Labels to apply to the kubernetes resource.
*
* When adding a dashboard to a Grafana instance using `grafana.addDashboard`,
* labels provided to Grafana will be automatically applied. Otherwise,
* labels must be added manually.
*
* @default - no labels
*/
readonly labels?: { [name: string]: string };

/**
* Namespace to apply to the kubernetes resource.
*
* When adding a dashboard to a Grafana instance using `grafana.addDashboard`,
* the namespace will be automatically inherited.
*
* @default - undefined (will be assigned to the 'default' namespace)
*/
readonly namespace?: string;

/**
* All other dashboard customizations.
* @see https://grafana.com/docs/grafana/latest/dashboards/json-model/
*/
readonly jsonModel?: { [key: string]: any };
}

/**
* A Grafana dashboard.
* @see https://grafana.com/docs/grafana/latest/http_api/dashboard/
*/
export class Dashboard extends Construct {
private readonly plugins: GrafanaPlugin[];
constructor(scope: Construct, id: string, props: DashboardProps) {
super(scope, id);

this.plugins = [];

const refreshRate = props.refreshRate ?? Duration.seconds(5);
const timeRange = props.timeRange ?? Duration.hours(6);
const dataSources = Object.entries(props.dataSourceVariables ?? {}).map(
([variable, name]) => ({ datasourceName: name, inputName: variable }),
);

const defaults = {
title: props.title,
id: null,
tags: [],
style: 'dark',
timezone: 'browser',
editable: true,
hideControls: false,
graphTooltip: 1,
panels: [],
time: {
from: `now-${timeRange.toSeconds()}s`,
to: 'now',
},
timepicker: {
time_options: [],
refresh_intervals: [],
},
templating: {
list: [],
},
annotations: {
list: [],
},
refresh: `${refreshRate.toSeconds()}s`,
schemaVersion: 17,
version: 0,
links: [],
} as any;

new GrafanaDashboard(this, 'Resource', {
metadata: {
labels: props.labels,
namespace: props.namespace,
},
spec: {
customFolderName: props.folder,
datasources: dataSources,
plugins: this.plugins,
json: JSON.stringify({
...defaults,
...props.jsonModel,
}, null, 2),
name: id,
},
});

if (props.plugins) {
this.addPlugins(...props.plugins);
}
}

/**
* Adds one or more plugins.
*/
public addPlugins(...plugins: GrafanaPlugin[]) {
for (const plugin of plugins) {
this.plugins.push(plugin);
}
}
}

export interface GrafanaPlugin {
/**
* Name of the plugin, e.g. "grafana-piechart-panel"
*/
readonly name: string;

/**
* Version of the plugin, e.g. "1.3.6"
*/
readonly version: string;
}
103 changes: 103 additions & 0 deletions src/datasource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Construct } from 'constructs';
import { GrafanaDataSource } from './imports/grafana-datasource';

/**
* Mode for accessing a data source.
* @see https://grafana.com/docs/grafana/latest/administration/provisioning/#example-data-source-config-file
*/
export enum AccessType {
/**
* Access via proxy.
*/
PROXY = 'proxy',

/**
* Access directly (via server or browser in UI).
*/
DIRECT = 'direct',
}

export interface DataSourceProps {
/**
* Name of the data source.
*/
readonly name: string;

/**
* Type of the data source.
*/
readonly type: string;

/**
* Access type of the data source.
*/
readonly access: AccessType;

/**
* Description of the data source.
* @default - no description
*/
readonly description?: string;

/**
* URL of the data source. Most resources besides the 'testdata' data source
* type require this field in order to retrieve data.
*
* @default - default url for data source type
*/
readonly url?: string;

/**
* Labels to apply to the kubernetes resource.
*
* When adding a data source to a Grafana instance using `grafana.addDataSource`,
* labels provided to Grafana will be automatically applied. Otherwise,
* labels must be added manually.
*
* @default - no labels
*/
readonly labels?: { [name: string]: string };

/**
* Namespace to apply to the kubernetes resource.
*
* When adding a data source to a Grafana instance using `grafana.addDataSource`,
* the namespace will be automatically inherited.
*
* @default - undefined (will be assigned to the 'default' namespace)
*/
readonly namespace?: string;
}

/**
* A Grafana data source.
* @see https://grafana.com/docs/grafana/latest/administration/provisioning/#example-data-source-config-file
*/
export class DataSource extends Construct {
/**
* Name of the data source.
*/
public readonly name: string;

constructor(scope: Construct, id: string, props: DataSourceProps) {
super(scope, id);

this.name = props.name;

new GrafanaDataSource(this, 'Resource', {
metadata: {
labels: props.labels,
namespace: props.namespace,
},
spec: {
name: props.name,
datasources: [{
name: props.name,
type: props.type,
access: props.access,
url: props.url,
}],
},
});
}
}
Loading

0 comments on commit fd26bcd

Please sign in to comment.