Skip to content

Commit b654719

Browse files
Merge branch 'main' of github.com:grafana/terraform-provider-grafana into alexander-akhmetov/alerting-enrichment
2 parents fdd3fc4 + 568969a commit b654719

15 files changed

+1195
-199
lines changed

docs/resources/contact_point.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,9 +523,13 @@ Optional:
523523
- `basic_auth_password` (String, Sensitive) The username to use in basic auth headers attached to the request. If omitted, basic auth will not be used.
524524
- `basic_auth_user` (String) The username to use in basic auth headers attached to the request. If omitted, basic auth will not be used.
525525
- `disable_resolve_message` (Boolean) Whether to disable sending resolve messages. Defaults to `false`.
526+
- `headers` (Map of String) Custom headers to attach to the request.
527+
- `hmac_config` (Block Set, Max: 1) HMAC signature configuration options. (see [below for nested schema](#nestedblock--webhook--hmac_config))
528+
- `http_config` (Block Set, Max: 1) Common HTTP client options. (see [below for nested schema](#nestedblock--webhook--http_config))
526529
- `http_method` (String) The HTTP method to use in the request. Defaults to `POST`.
527530
- `max_alerts` (Number) The maximum number of alerts to send in a single request. This can be helpful in limiting the size of the request body. The default is 0, which indicates no limit.
528531
- `message` (String) Custom message. You can use template variables.
532+
- `payload` (Block Set, Max: 1) Optionally provide a templated payload. Overrides 'Message' and 'Title' field. (see [below for nested schema](#nestedblock--webhook--payload))
529533
- `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`.
530534
- `title` (String) Templated title of the message.
531535
- `tls_config` (Map of String, Sensitive) Allows configuring TLS for the webhook notifier.
@@ -534,6 +538,78 @@ Read-Only:
534538

535539
- `uid` (String) The UID of the contact point.
536540

541+
<a id="nestedblock--webhook--hmac_config"></a>
542+
### Nested Schema for `webhook.hmac_config`
543+
544+
Required:
545+
546+
- `secret` (String, Sensitive) The secret key used to generate the HMAC signature.
547+
548+
Optional:
549+
550+
- `header` (String) The header in which the HMAC signature will be included. Defaults to `X-Grafana-Alerting-Signature`.
551+
- `timestamp_header` (String) If set, the timestamp will be included in the HMAC signature. The value should be the name of the header to use.
552+
553+
554+
<a id="nestedblock--webhook--http_config"></a>
555+
### Nested Schema for `webhook.http_config`
556+
557+
Optional:
558+
559+
- `oauth2` (Block Set, Max: 1) OAuth2 configuration options. (see [below for nested schema](#nestedblock--webhook--http_config--oauth2))
560+
561+
<a id="nestedblock--webhook--http_config--oauth2"></a>
562+
### Nested Schema for `webhook.http_config.oauth2`
563+
564+
Required:
565+
566+
- `client_id` (String) Client ID to use when authenticating.
567+
- `client_secret` (String, Sensitive) Client secret to use when authenticating.
568+
- `token_url` (String) URL for the access token endpoint.
569+
570+
Optional:
571+
572+
- `endpoint_params` (Map of String) Optional parameters to append to the access token request.
573+
- `proxy_config` (Block Set, Max: 1) Optional proxy configuration for OAuth2 requests. (see [below for nested schema](#nestedblock--webhook--http_config--oauth2--proxy_config))
574+
- `scopes` (List of String) Optional scopes to request when obtaining an access token.
575+
- `tls_config` (Block Set, Max: 1) Optional TLS configuration options for OAuth2 requests. (see [below for nested schema](#nestedblock--webhook--http_config--oauth2--tls_config))
576+
577+
<a id="nestedblock--webhook--http_config--oauth2--proxy_config"></a>
578+
### Nested Schema for `webhook.http_config.oauth2.proxy_config`
579+
580+
Optional:
581+
582+
- `no_proxy` (String) Comma-separated list of addresses that should not use a proxy.
583+
- `proxy_connect_header` (Map of String) Optional headers to send to proxies during CONNECT requests.
584+
- `proxy_from_environment` (Boolean) Use environment HTTP_PROXY, HTTPS_PROXY and NO_PROXY to determine proxies. Defaults to `false`.
585+
- `proxy_url` (String) HTTP proxy server to use to connect to the targets.
586+
587+
588+
<a id="nestedblock--webhook--http_config--oauth2--tls_config"></a>
589+
### Nested Schema for `webhook.http_config.oauth2.tls_config`
590+
591+
Optional:
592+
593+
- `ca_certificate` (String, Sensitive) Certificate in PEM format to use when verifying the server's certificate chain.
594+
- `client_certificate` (String, Sensitive) Client certificate in PEM format to use when connecting to the server.
595+
- `client_key` (String, Sensitive) Client key in PEM format to use when connecting to the server.
596+
- `insecure_skip_verify` (Boolean) Do not verify the server's certificate chain and host name. Defaults to `false`.
597+
598+
599+
600+
601+
<a id="nestedblock--webhook--payload"></a>
602+
### Nested Schema for `webhook.payload`
603+
604+
Required:
605+
606+
- `template` (String) Custom payload template.
607+
608+
Optional:
609+
610+
- `vars` (Map of String) Optionally provide a variables to be used in the payload template. They will be available in the template as `.Vars.<variable_name>`.
611+
612+
537613

538614
<a id="nestedblock--wecom"></a>
539615
### Nested Schema for `wecom`
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
resource "grafana_contact_point" "receiver_types" {
2+
name = "Receiver Types since v11.6"
3+
4+
webhook {
5+
url = "http://hmac-minimal-webhook-url"
6+
hmac_config {
7+
secret = "test-hmac-minimal-secret"
8+
}
9+
}
10+
11+
webhook {
12+
url = "http://hmac-webhook-url"
13+
hmac_config {
14+
secret = "test-hmac-secret"
15+
header = "X-Grafana-Alerting-Signature"
16+
timestamp_header = "X-Grafana-Alerting-Timestamp"
17+
}
18+
}
19+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
resource "grafana_contact_point" "receiver_types" {
2+
name = "Receiver Types since v12.0"
3+
4+
webhook {
5+
url = "http://my-url"
6+
headers = {
7+
Content-Type = "test-content-type"
8+
X-Test-Header = "test-header-value"
9+
}
10+
payload {
11+
template = "{{ .Receiver }}: {{ .Vars.var1 }}"
12+
vars = {
13+
var1 = "variable value"
14+
}
15+
}
16+
}
17+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
resource "grafana_contact_point" "receiver_types" {
2+
name = "Receiver Types since v12.1"
3+
4+
webhook {
5+
url = "http://my-url"
6+
http_method = "POST"
7+
basic_auth_user = "user"
8+
basic_auth_password = "password"
9+
max_alerts = 100
10+
message = "Custom message"
11+
title = "Custom title"
12+
tls_config = {
13+
insecure_skip_verify = true
14+
ca_certificate = "ca.crt"
15+
client_certificate = "client.crt"
16+
client_key = "client.key"
17+
}
18+
http_config {
19+
oauth2 {
20+
client_id = "client_id"
21+
client_secret = "client_secret"
22+
token_url = "http://oauth2-token-url"
23+
scopes = ["scope1", "scope2"]
24+
endpoint_params = {
25+
"param1" = "value1"
26+
"param2" = "value2"
27+
}
28+
proxy_config {
29+
proxy_url = "http://proxy-url"
30+
proxy_from_environment = false
31+
no_proxy = "localhost"
32+
proxy_connect_header = {
33+
"X-Proxy-Header" = "proxy-value"
34+
}
35+
}
36+
tls_config {
37+
insecure_skip_verify = true
38+
ca_certificate = <<EOF
39+
-----BEGIN CERTIFICATE-----
40+
MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx
41+
MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60
42+
2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3
43+
WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML
44+
-----END CERTIFICATE-----
45+
EOF
46+
client_certificate = <<EOF
47+
-----BEGIN CERTIFICATE-----
48+
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
49+
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
50+
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
51+
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
52+
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
53+
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
54+
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
55+
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
56+
6MF9+Yw1Yy0t
57+
-----END CERTIFICATE-----
58+
EOF
59+
client_key = <<EOF
60+
-----BEGIN EC PRIVATE KEY-----
61+
MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49
62+
AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q
63+
EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
64+
-----END EC PRIVATE KEY-----
65+
EOF
66+
}
67+
}
68+
}
69+
}
70+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ require (
1212
github.com/grafana/authlib/claims v0.0.0-20250120084028-e3328c576437
1313
github.com/grafana/fleet-management-api v1.0.0
1414
github.com/grafana/grafana-app-sdk v0.40.3
15-
github.com/grafana/grafana-asserts-public-clients/go/gcom v0.0.0-20250805165836-14e16b51b910
15+
github.com/grafana/grafana-asserts-public-clients/go/gcom v0.0.0-20250811125322-247815da58ca
1616
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20250526074454-7ec66e02e4bb
1717
github.com/grafana/grafana-openapi-client-go v0.0.0-20250617151817-c0f8cbb88d5c
1818
github.com/grafana/grafana/apps/alerting/alertenrichment v0.0.0-20250904171753-3d6d6326866f

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ github.com/grafana/grafana-app-sdk v0.40.3 h1:JFo7uAfbAJUfZ9neD7/4sODKm1xgu9zhck
178178
github.com/grafana/grafana-app-sdk v0.40.3/go.mod h1:j0KzHo3Sa6kd+lnwSScBNoV9Vobkg/YY9HtEjxpyPrk=
179179
github.com/grafana/grafana-app-sdk/logging v0.40.2 h1:HQ1+y9Od92iMbWWB54QxiYpNtCvYGUVpyxvxZ7ywB1k=
180180
github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
181-
github.com/grafana/grafana-asserts-public-clients/go/gcom v0.0.0-20250805165836-14e16b51b910 h1:2OfDIhMtXWWVQcDp9cq/VMSBOJJfDek9450rcsV+qLg=
182-
github.com/grafana/grafana-asserts-public-clients/go/gcom v0.0.0-20250805165836-14e16b51b910/go.mod h1:EL/5hluCvj6EDjkUfoClLKSKDoCoDowZUety28jhxQI=
181+
github.com/grafana/grafana-asserts-public-clients/go/gcom v0.0.0-20250811125322-247815da58ca h1:GVzyCTi3rqvjK42b++lFjabG2zsrLvyAbbR43dWP6s0=
182+
github.com/grafana/grafana-asserts-public-clients/go/gcom v0.0.0-20250811125322-247815da58ca/go.mod h1:EL/5hluCvj6EDjkUfoClLKSKDoCoDowZUety28jhxQI=
183183
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20250526074454-7ec66e02e4bb h1:rmYEnCXHNQbRsuzc5jCX5qkBqFF37c5RCHlyqAAPJZo=
184184
github.com/grafana/grafana-com-public-clients/go/gcom v0.0.0-20250526074454-7ec66e02e4bb/go.mod h1:sYWkB3NhyirQJfy3wtNQ29UYjoHbRlJlYhqN1jNsC5g=
185185
github.com/grafana/grafana-openapi-client-go v0.0.0-20250617151817-c0f8cbb88d5c h1:jox7J0BnJmcZJp8lp631u4gjDEoIfpi6O3yrpiXNTtg=
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package asserts
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math"
7+
"math/rand"
8+
"time"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
12+
13+
assertsapi "github.com/grafana/grafana-asserts-public-clients/go/gcom"
14+
"github.com/grafana/terraform-provider-grafana/v4/internal/common"
15+
)
16+
17+
// validateAssertsClient checks if the Asserts API client is properly configured
18+
func validateAssertsClient(meta interface{}) (*assertsapi.APIClient, int64, diag.Diagnostics) {
19+
client := meta.(*common.Client).AssertsAPIClient
20+
if client == nil {
21+
return nil, 0, diag.Errorf("Asserts API client is not configured")
22+
}
23+
24+
stackID := meta.(*common.Client).GrafanaStackID
25+
if stackID == 0 {
26+
return nil, 0, diag.Errorf("stack_id must be set in provider configuration for Asserts resources")
27+
}
28+
29+
return client, stackID, nil
30+
}
31+
32+
// retryReadFunc is a function that performs a read operation with retry logic
33+
type retryReadFunc func(retryCount, maxRetries int) *retry.RetryError
34+
35+
// withRetryRead wraps a read operation with consistent retry logic and exponential backoff
36+
func withRetryRead(ctx context.Context, operation retryReadFunc) error {
37+
retryCount := 0
38+
maxRetries := 40
39+
40+
// Increase overall timeout to better handle eventual consistency when
41+
// multiple resources are created concurrently (e.g., stress tests)
42+
return retry.RetryContext(ctx, 600*time.Second, func() *retry.RetryError {
43+
retryCount++
44+
45+
// Backoff with jitter to reduce request stampeding
46+
var baseSleep time.Duration
47+
if retryCount == 1 {
48+
baseSleep = 1 * time.Second
49+
} else {
50+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s (capped at 16s)
51+
baseSleep = time.Duration(1<<int(math.Min(float64(retryCount-2), 4))) * time.Second
52+
}
53+
54+
// Apply jitter: sleep in [base/2, base]
55+
minSleep := baseSleep / 2
56+
maxJitter := baseSleep - minSleep
57+
if maxJitter > 0 {
58+
//nolint:gosec // Using math/rand for jitter in retry logic, not cryptographic purposes
59+
j := time.Duration(rand.Int63n(int64(maxJitter)))
60+
time.Sleep(minSleep + j)
61+
} else {
62+
time.Sleep(baseSleep)
63+
}
64+
65+
// Execute the operation with retry count
66+
return operation(retryCount, maxRetries)
67+
})
68+
}
69+
70+
// createRetryableError creates a retryable error with consistent formatting
71+
func createRetryableError(resourceType, resourceName string, retryCount, maxRetries int) *retry.RetryError {
72+
return retry.RetryableError(fmt.Errorf("%s %s not found (attempt %d/%d)", resourceType, resourceName, retryCount, maxRetries))
73+
}
74+
75+
// createNonRetryableError creates a non-retryable error with consistent formatting
76+
func createNonRetryableError(resourceType, resourceName string, retryCount int) *retry.RetryError {
77+
return retry.NonRetryableError(fmt.Errorf("%s %s not found after %d retries - may indicate a permanent issue", resourceType, resourceName, retryCount))
78+
}
79+
80+
// createAPIError creates a retryable or non-retryable API error based on retry count
81+
func createAPIError(operation string, retryCount, maxRetries int, err error) *retry.RetryError {
82+
if retryCount >= maxRetries {
83+
return retry.NonRetryableError(fmt.Errorf("failed to %s after %d retries: %w", operation, retryCount, err))
84+
}
85+
return retry.RetryableError(fmt.Errorf("failed to %s: %w", operation, err))
86+
}

0 commit comments

Comments
 (0)