Skip to content
Open
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
171 changes: 171 additions & 0 deletions docs/components/Grafana.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { CardGrid, LinkCard } from "@astrojs/starlight/components";
## Actions

<CardGrid>
<LinkCard title="Create Silence" href="#create-silence" description="Create a new silence in the Grafana Alertmanager to suppress alert notifications" />
<LinkCard title="Delete Silence" href="#delete-silence" description="Expire (delete) an existing Grafana Alertmanager silence by ID" />
<LinkCard title="Get Silence" href="#get-silence" description="Retrieve a single Grafana Alertmanager silence by ID" />
<LinkCard title="List Silences" href="#list-silences" description="List active and pending silences from the Grafana Alertmanager" />
<LinkCard title="Query Data Source" href="#query-data-source" description="Execute a query against a Grafana data source and return the result" />
</CardGrid>

Expand Down Expand Up @@ -85,6 +89,173 @@ The trigger emits the full Grafana webhook payload, including:
}
```

<a id="create-silence"></a>

## Create Silence

The Create Silence component creates a new Alertmanager silence in Grafana, suppressing alert notifications that match the configured matchers during the specified time window.

### Use Cases

- **Deploy window**: Suppress noisy alerts during a planned maintenance or deployment window
- **Incident management**: Prevent alert storms from flooding on-call channels while an incident is being worked on
- **Testing**: Silence alerts during load tests or chaos experiments

### Configuration

- **Matchers**: One or more label matchers that identify which alerts to silence (required)
- **Starts At**: The start of the silence window (required)
- **Ends At**: The end of the silence window (required)
- **Comment**: A description of why the silence is being created (required)
- **Created By**: The author name recorded on the silence (optional, defaults to "superplane")

### Output

Returns the ID of the newly created silence.

### Example Output

```json
{
"data": {
"silenceId": "a3e5c2d1-8b4f-4e1a-9c7d-2f0e6b3a1d5c"
},
"timestamp": "2026-03-31T10:24:30Z",
"type": "grafana.silence.created"
}
```

<a id="delete-silence"></a>

## Delete Silence

The Delete Silence component expires an existing silence in Grafana Alertmanager.

### Use Cases

- **End a maintenance window early**: Remove a silence once deployment or maintenance completes ahead of schedule
- **Automated cleanup**: Expire silences created by automation after the condition they covered has resolved

### Configuration

- **Silence ID**: The unique ID of the silence to expire (required)

### Output

Returns the silence ID and a confirmation that the silence was deleted.

### Example Output

```json
{
"data": {
"deleted": true,
"silenceId": "a3e5c2d1-8b4f-4e1a-9c7d-2f0e6b3a1d5c"
},
"timestamp": "2026-03-31T10:24:30Z",
"type": "grafana.silence.deleted"
}
```

<a id="get-silence"></a>

## Get Silence

The Get Silence component fetches the details of a single silence from Grafana Alertmanager using its ID.

### Use Cases

- **Inspect a silence**: Retrieve full details of a silence including state, comment, matchers, and times
- **Verify a silence**: Confirm a silence is still active before taking action in a workflow

### Configuration

- **Silence ID**: The unique ID of the silence to retrieve (required)

### Output

Returns the silence object including ID, state, comment, matchers, start/end times, and the author.

### Example Output

```json
{
"data": {
"comment": "Deploy window for v2.1.0",
"createdBy": "devops-bot",
"endsAt": "2026-03-31T11:00:00.000Z",
"id": "a3e5c2d1-8b4f-4e1a-9c7d-2f0e6b3a1d5c",
"matchers": [
{
"isEqual": true,
"isRegex": false,
"name": "env",
"value": "production"
}
],
"startsAt": "2026-03-31T10:00:00.000Z",
"status": {
"state": "active"
},
"updatedAt": "2026-03-31T10:00:00.000Z"
},
"timestamp": "2026-03-31T10:24:30Z",
"type": "grafana.silence"
}
```

<a id="list-silences"></a>

## List Silences

The List Silences component retrieves silences from Grafana Alertmanager.

### Use Cases

- **Audit**: Review all currently active or pending silences in your Grafana instance
- **Detect if already muted**: Check whether a specific alert or label set is already silenced before creating a duplicate
- **Workflow logic**: Branch on silence state — e.g. skip escalation if an alert is already silenced

### Configuration

- **Filter**: Optional label matcher string to filter silences (e.g. `alertname=~"High.*"`)

### Output

Returns a list of silence objects, each including ID, state, comment, matchers, start/end times, and the author.

### Example Output

```json
{
"data": {
"silences": [
{
"comment": "Deploy window for v2.1.0",
"createdBy": "devops-bot",
"endsAt": "2026-03-31T11:00:00.000Z",
"id": "a3e5c2d1-8b4f-4e1a-9c7d-2f0e6b3a1d5c",
"matchers": [
{
"isEqual": true,
"isRegex": false,
"name": "env",
"value": "production"
}
],
"startsAt": "2026-03-31T10:00:00.000Z",
"status": {
"state": "active"
},
"updatedAt": "2026-03-31T10:00:00.000Z"
}
]
},
"timestamp": "2026-03-31T10:24:30Z",
"type": "grafana.silences"
}
```

<a id="query-data-source"></a>

## Query Data Source
Expand Down
144 changes: 144 additions & 0 deletions pkg/integrations/grafana/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ type DataSource struct {
Name string `json:"name"`
}

type Silence struct {
ID string `json:"id"`
Status SilenceStatus `json:"status"`
Comment string `json:"comment"`
CreatedBy string `json:"createdBy"`
StartsAt string `json:"startsAt"`
EndsAt string `json:"endsAt"`
UpdatedAt string `json:"updatedAt"`
Matchers []SilenceMatcher `json:"matchers"`
}

type SilenceStatus struct {
State string `json:"state"`
}

type SilenceMatcher struct {
Name string `json:"name"`
Value string `json:"value"`
IsRegex bool `json:"isRegex"`
IsEqual bool `json:"isEqual"`
}

type apiStatusError struct {
Operation string
StatusCode int
Expand Down Expand Up @@ -546,6 +568,128 @@ func (c *Client) RemoveNotificationPolicyRoute(contactPointName string) error {
return c.putNotificationPolicies(root)
}

func (c *Client) ListSilences(filter string) ([]Silence, error) {
path := "/api/alertmanager/grafana/api/v2/silences"
if f := strings.TrimSpace(filter); f != "" {
q := url.Values{}
q.Set("filter", f)
path = path + "?" + q.Encode()
}

responseBody, status, err := c.execRequest(http.MethodGet, path, nil, "")
if err != nil {
return nil, fmt.Errorf("error listing silences: %v", err)
}

if status < 200 || status >= 300 {
return nil, newAPIStatusError("grafana silence list", status, responseBody)
}

var silences []Silence
if err := json.Unmarshal(responseBody, &silences); err != nil {
return nil, fmt.Errorf("error parsing silences response: %v", err)
}

return silences, nil
}

func (c *Client) GetSilence(id string) (*Silence, error) {
responseBody, status, err := c.execRequest(
http.MethodGet,
fmt.Sprintf("/api/alertmanager/grafana/api/v2/silence/%s", url.PathEscape(id)),
nil, "",
)
if err != nil {
return nil, fmt.Errorf("error getting silence: %v", err)
}

if status < 200 || status >= 300 {
return nil, newAPIStatusError("grafana silence get", status, responseBody)
}

var silence Silence
if err := json.Unmarshal(responseBody, &silence); err != nil {
return nil, fmt.Errorf("error parsing silence response: %v", err)
}

return &silence, nil
}

func (c *Client) CreateSilence(matchers []SilenceMatcher, startsAt, endsAt, comment, createdBy string) (string, error) {
if strings.TrimSpace(createdBy) == "" {
createdBy = "superplane"
}

payload := map[string]any{
"matchers": matchers,
"startsAt": startsAt,
"endsAt": endsAt,
"comment": comment,
"createdBy": createdBy,
}

body, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("error marshaling silence payload: %v", err)
}

responseBody, status, err := c.execRequest(
http.MethodPost,
"/api/alertmanager/grafana/api/v2/silences",
bytes.NewReader(body),
"application/json",
)
if err != nil {
return "", fmt.Errorf("error creating silence: %v", err)
}

if status < 200 || status >= 300 {
return "", newAPIStatusError("grafana silence create", status, responseBody)
}

id, err := parseCreateSilenceResponseBody(responseBody)
if err != nil {
return "", err
}

return id, nil
}

func parseCreateSilenceResponseBody(responseBody []byte) (string, error) {
var result struct {
SilenceID string `json:"silenceID"`
SilenceId string `json:"silenceId"`
}
if err := json.Unmarshal(responseBody, &result); err != nil {
return "", fmt.Errorf("error parsing create silence response: %v", err)
}
if id := strings.TrimSpace(result.SilenceID); id != "" {
return id, nil
}
if id := strings.TrimSpace(result.SilenceId); id != "" {
return id, nil
}

return "", fmt.Errorf("create silence response missing silence id")
}

func (c *Client) DeleteSilence(id string) error {
responseBody, status, err := c.execRequest(
http.MethodDelete,
fmt.Sprintf("/api/alertmanager/grafana/api/v2/silence/%s", url.PathEscape(id)),
nil, "",
)
if err != nil {
return fmt.Errorf("error deleting silence: %v", err)
}

if status == http.StatusNotFound || status == http.StatusNoContent || (status >= 200 && status < 300) {
return nil
}

return newAPIStatusError("grafana silence delete", status, responseBody)
}

func (c *Client) ListDataSources() ([]DataSource, error) {
responseBody, status, err := c.execRequest(http.MethodGet, "/api/datasources", nil, "")
if err != nil {
Expand Down
Loading
Loading