Skip to content

Commit 2902e8f

Browse files
synthetic_monitoring_installation: Better integration with stack (#898)
* `synthetic_monitoring_installation`: Better integration with stack Closes #867 Currently, the way to configure this is very convoluted: - It requires three attributes from the stack - It doesn't set the SM API URL. For that, the user must use the provider level sm_api_url. This isn't easy to figure out In this PR, I squashed all three stack attributes into one and added a way to configure the SM API URL * Update examples/provider/provider-sm.tf Co-authored-by: Leandro López <[email protected]> * Extract SM API URLs + fix bad examples --------- Co-authored-by: Leandro López <[email protected]>
1 parent 2e11cc0 commit 2902e8f

File tree

8 files changed

+101
-45
lines changed

8 files changed

+101
-45
lines changed

docs/index.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,17 +121,14 @@ resource "grafana_cloud_api_key" "metrics_publish" {
121121
resource "grafana_synthetic_monitoring_installation" "sm_stack" {
122122
provider = grafana.cloud
123123
124-
stack_id = grafana_cloud_stack.sm_stack.id
125-
metrics_instance_id = grafana_cloud_stack.sm_stack.prometheus_user_id
126-
logs_instance_id = grafana_cloud_stack.sm_stack.logs_user_id
127-
metrics_publisher_key = grafana_cloud_api_key.metrics_publish.key
124+
stack_id = grafana_cloud_stack.sm_stack.id
128125
}
129126
130127
// Step 3: Interact with Synthetic Monitoring
131128
provider "grafana" {
132129
alias = "sm"
133130
sm_access_token = grafana_synthetic_monitoring_installation.sm_stack.sm_access_token
134-
sm_url = "<synthetic-monitoring-api-url>"
131+
sm_url = grafana_synthetic_monitoring_installation.sm_stack.stack_sm_api_url
135132
}
136133
137134
data "grafana_synthetic_monitoring_probes" "main" {

docs/resources/synthetic_monitoring_installation.md

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ description: |-
66
Sets up Synthetic Monitoring on a Grafana cloud stack and generates a token.
77
Once a Grafana Cloud stack is created, a user can either use this resource or go into the UI to install synthetic monitoring.
88
This resource cannot be imported but it can be used on an existing Synthetic Monitoring installation without issues.
9+
Note that this resource must be used on a provider configured with Grafana Cloud credentials.
910
Official documentation https://grafana.com/docs/grafana-cloud/synthetic-monitoring/installation/API documentation https://github.com/grafana/synthetic-monitoring-api-go-client/blob/main/docs/API.md#apiv1registerinstall
1011
---
1112

@@ -15,6 +16,8 @@ Sets up Synthetic Monitoring on a Grafana cloud stack and generates a token.
1516
Once a Grafana Cloud stack is created, a user can either use this resource or go into the UI to install synthetic monitoring.
1617
This resource cannot be imported but it can be used on an existing Synthetic Monitoring installation without issues.
1718

19+
**Note that this resource must be used on a provider configured with Grafana Cloud credentials.**
20+
1821
* [Official documentation](https://grafana.com/docs/grafana-cloud/synthetic-monitoring/installation/)
1922
* [API documentation](https://github.com/grafana/synthetic-monitoring-api-go-client/blob/main/docs/API.md#apiv1registerinstall)
2023

@@ -34,10 +37,14 @@ resource "grafana_cloud_api_key" "metrics_publish" {
3437
}
3538
3639
resource "grafana_synthetic_monitoring_installation" "sm_stack" {
37-
stack_id = grafana_cloud_stack.sm_stack.id
38-
metrics_instance_id = grafana_cloud_stack.sm_stack.prometheus_user_id
39-
logs_instance_id = grafana_cloud_stack.sm_stack.logs_user_id
40-
metrics_publisher_key = grafana_cloud_api_key.metrics_publish.key
40+
stack_id = grafana_cloud_stack.sm_stack.id
41+
}
42+
43+
// Create a new provider instance to interact with Synthetic Monitoring
44+
provider "grafana" {
45+
alias = "sm"
46+
sm_access_token = grafana_synthetic_monitoring_installation.sm_stack.sm_access_token
47+
sm_url = grafana_synthetic_monitoring_installation.sm_stack.stack_sm_api_url
4148
}
4249
```
4350

@@ -46,10 +53,14 @@ resource "grafana_synthetic_monitoring_installation" "sm_stack" {
4653

4754
### Required
4855

49-
- `logs_instance_id` (Number) The ID of the logs instance to install SM on (stack's `logs_user_id` attribute).
50-
- `metrics_instance_id` (Number) The ID of the metrics instance to install SM on (stack's `prometheus_user_id` attribute).
5156
- `metrics_publisher_key` (String, Sensitive) The Cloud API Key with the `MetricsPublisher` role used to publish metrics to the SM API
52-
- `stack_id` (Number) The ID of the stack to install SM on.
57+
- `stack_id` (String) The ID or slug of the stack to install SM on.
58+
59+
### Optional
60+
61+
- `logs_instance_id` (Number, Deprecated) Deprecated: Not used anymore.
62+
- `metrics_instance_id` (Number, Deprecated) Deprecated: Not used anymore.
63+
- `stack_sm_api_url` (String) The URL of the SM API to install SM on. This depends on the stack region, find the list of API URLs here: https://grafana.com/docs/grafana-cloud/synthetic-monitoring/private-probes/#probe-api-server-url. A static mapping exists in the provider but it may not contain all the regions. If it does contain the stack's region, this field is computed automatically and readable.
5364

5465
### Read-Only
5566

examples/provider/provider-sm.tf

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,14 @@ resource "grafana_cloud_api_key" "metrics_publish" {
2525
resource "grafana_synthetic_monitoring_installation" "sm_stack" {
2626
provider = grafana.cloud
2727

28-
stack_id = grafana_cloud_stack.sm_stack.id
29-
metrics_instance_id = grafana_cloud_stack.sm_stack.prometheus_user_id
30-
logs_instance_id = grafana_cloud_stack.sm_stack.logs_user_id
31-
metrics_publisher_key = grafana_cloud_api_key.metrics_publish.key
28+
stack_id = grafana_cloud_stack.sm_stack.id
3229
}
3330

3431
// Step 3: Interact with Synthetic Monitoring
3532
provider "grafana" {
3633
alias = "sm"
3734
sm_access_token = grafana_synthetic_monitoring_installation.sm_stack.sm_access_token
38-
sm_url = "<synthetic-monitoring-api-url>"
35+
sm_url = grafana_synthetic_monitoring_installation.sm_stack.stack_sm_api_url
3936
}
4037

4138
data "grafana_synthetic_monitoring_probes" "main" {

examples/resources/grafana_synthetic_monitoring_installation/resource.tf

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ resource "grafana_cloud_api_key" "metrics_publish" {
1111
}
1212

1313
resource "grafana_synthetic_monitoring_installation" "sm_stack" {
14-
stack_id = grafana_cloud_stack.sm_stack.id
15-
metrics_instance_id = grafana_cloud_stack.sm_stack.prometheus_user_id
16-
logs_instance_id = grafana_cloud_stack.sm_stack.logs_user_id
17-
metrics_publisher_key = grafana_cloud_api_key.metrics_publish.key
14+
stack_id = grafana_cloud_stack.sm_stack.id
15+
}
16+
17+
// Create a new provider instance to interact with Synthetic Monitoring
18+
provider "grafana" {
19+
alias = "sm"
20+
sm_access_token = grafana_synthetic_monitoring_installation.sm_stack.sm_access_token
21+
sm_url = grafana_synthetic_monitoring_installation.sm_stack.stack_sm_api_url
1822
}

internal/common/client.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ type Client struct {
1515
GrafanaAPI *gapi.Client
1616
GrafanaCloudAPI *gapi.Client
1717

18-
SMAPI *SMAPI.Client
19-
SMAPIURL string
18+
SMAPI *SMAPI.Client
2019

2120
MLAPI *mlapi.Client
2221

internal/provider/provider.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,8 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema
318318
return nil, diag.FromErr(err)
319319
}
320320
}
321-
c.SMAPIURL = d.Get("sm_url").(string)
322321
if smToken := d.Get("sm_access_token").(string); smToken != "" {
323-
c.SMAPI = SMAPI.NewClient(c.SMAPIURL, smToken, nil)
322+
c.SMAPI = SMAPI.NewClient(d.Get("sm_url").(string), smToken, nil)
324323
}
325324
if d.Get("oncall_access_token").(string) != "" {
326325
var onCallClient *onCallAPI.Client

internal/resources/cloud/resource_synthetic_monitoring_installation.go

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,31 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"strconv"
8+
"strings"
79

10+
gapi "github.com/grafana/grafana-api-golang-client"
811
SMAPI "github.com/grafana/synthetic-monitoring-api-go-client"
912
"github.com/grafana/terraform-provider-grafana/internal/common"
1013
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1114
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1215
)
1316

17+
// TODO: Automate finding the correct API URL based on the stack region
18+
var smAPIURLs = map[string]string{
19+
"us": "https://synthetic-monitoring-api.grafana.net",
20+
"us-azure": "https://synthetic-monitoring-api-us-central2.grafana.net",
21+
"eu": "https://synthetic-monitoring-api-eu-west.grafana.net",
22+
"au": "https://synthetic-monitoring-api-au-southeast.grafana.net",
23+
"prod-ap-southeast-0": "https://synthetic-monitoring-api-ap-southeast-0.grafana.net",
24+
"prod-gb-south-0": "https://synthetic-monitoring-api-gb-south.grafana.net",
25+
"prod-eu-west-2": "https://synthetic-monitoring-api-eu-west-2.grafana.net",
26+
"prod-eu-west-3": "https://synthetic-monitoring-api-eu-west-3.grafana.net",
27+
"prod-ap-south-0": "https://synthetic-monitoring-api-ap-south-0.grafana.net",
28+
"prod-sa-east-0": "https://synthetic-monitoring-api-sa-east-0.grafana.net",
29+
"prod-us-east-0": "https://synthetic-monitoring-api-us-east-0.grafana.net",
30+
}
31+
1432
func ResourceInstallation() *schema.Resource {
1533
return &schema.Resource{
1634

@@ -19,13 +37,16 @@ Sets up Synthetic Monitoring on a Grafana cloud stack and generates a token.
1937
Once a Grafana Cloud stack is created, a user can either use this resource or go into the UI to install synthetic monitoring.
2038
This resource cannot be imported but it can be used on an existing Synthetic Monitoring installation without issues.
2139
40+
**Note that this resource must be used on a provider configured with Grafana Cloud credentials.**
41+
2242
* [Official documentation](https://grafana.com/docs/grafana-cloud/synthetic-monitoring/installation/)
2343
* [API documentation](https://github.com/grafana/synthetic-monitoring-api-go-client/blob/main/docs/API.md#apiv1registerinstall)
2444
`,
2545
CreateContext: ResourceInstallationCreate,
46+
ReadContext: ResourceInstallationRead,
47+
UpdateContext: ResourceInstallationRead,
2648
DeleteContext: ResourceInstallationDelete,
2749

28-
ReadContext: func(ctx context.Context, rd *schema.ResourceData, i interface{}) diag.Diagnostics { return nil },
2950
Schema: map[string]*schema.Schema{
3051
"metrics_publisher_key": {
3152
Type: schema.TypeString,
@@ -34,23 +55,30 @@ This resource cannot be imported but it can be used on an existing Synthetic Mon
3455
ForceNew: true,
3556
Description: "The Cloud API Key with the `MetricsPublisher` role used to publish metrics to the SM API",
3657
},
58+
"stack_sm_api_url": {
59+
Type: schema.TypeString,
60+
Optional: true,
61+
Computed: true,
62+
ForceNew: true,
63+
Description: "The URL of the SM API to install SM on. This depends on the stack region, find the list of API URLs here: https://grafana.com/docs/grafana-cloud/synthetic-monitoring/private-probes/#probe-api-server-url. A static mapping exists in the provider but it may not contain all the regions. If it does contain the stack's region, this field is computed automatically and readable.",
64+
},
3765
"stack_id": {
38-
Type: schema.TypeInt,
66+
Type: schema.TypeString,
3967
Required: true,
4068
ForceNew: true,
41-
Description: "The ID of the stack to install SM on.",
69+
Description: "The ID or slug of the stack to install SM on.",
4270
},
4371
"metrics_instance_id": {
4472
Type: schema.TypeInt,
45-
Required: true,
46-
ForceNew: true,
47-
Description: "The ID of the metrics instance to install SM on (stack's `prometheus_user_id` attribute).",
73+
Optional: true,
74+
Deprecated: "Not used anymore.",
75+
Description: "Deprecated: Not used anymore.",
4876
},
4977
"logs_instance_id": {
5078
Type: schema.TypeInt,
51-
Required: true,
52-
ForceNew: true,
53-
Description: "The ID of the logs instance to install SM on (stack's `logs_user_id` attribute).",
79+
Optional: true,
80+
Deprecated: "Not used anymore.",
81+
Description: "Deprecated: Not used anymore.",
5482
},
5583
"sm_access_token": {
5684
Type: schema.TypeString,
@@ -62,22 +90,44 @@ This resource cannot be imported but it can be used on an existing Synthetic Mon
6290
}
6391

6492
func ResourceInstallationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
65-
c := SMAPI.NewClient(meta.(*common.Client).SMAPIURL, "", nil)
66-
stackID, metricsID, logsID := d.Get("stack_id").(int), d.Get("metrics_instance_id").(int), d.Get("logs_instance_id").(int)
67-
resp, err := c.Install(ctx, int64(stackID), int64(metricsID), int64(logsID), d.Get("metrics_publisher_key").(string))
93+
cloudClient := meta.(*common.Client).GrafanaCloudAPI
94+
var stack gapi.Stack
95+
96+
stackIDInt, err := strconv.ParseInt(d.Get("stack_id").(string), 10, 64)
97+
if err == nil {
98+
stack, err = cloudClient.StackByID(stackIDInt)
99+
} else {
100+
stack, err = cloudClient.StackBySlug(d.Get("stack_id").(string))
101+
}
68102
if err != nil {
69103
return diag.FromErr(err)
70104
}
71-
d.SetId(fmt.Sprintf("%d-%d-%d", stackID, metricsID, logsID))
105+
106+
apiURL := d.Get("stack_sm_api_url").(string)
107+
if apiURL == "" {
108+
apiURL = smAPIURLs[stack.RegionSlug]
109+
}
110+
if apiURL == "" {
111+
return diag.Errorf("could not find a valid SM API URL for stack region %s", stack.RegionSlug)
112+
}
113+
114+
smClient := SMAPI.NewClient(apiURL, "", nil)
115+
stackID, metricsID, logsID := stack.ID, int64(stack.HmInstancePromID), int64(stack.HlInstanceID)
116+
resp, err := smClient.Install(ctx, stackID, metricsID, logsID, d.Get("metrics_publisher_key").(string))
117+
if err != nil {
118+
return diag.FromErr(err)
119+
}
120+
d.SetId(fmt.Sprintf("%s;%d", apiURL, stackID))
72121
d.Set("sm_access_token", resp.AccessToken)
73-
return nil
122+
d.Set("stack_sm_api_url", apiURL)
123+
return ResourceInstallationRead(ctx, d, meta)
74124
}
75125

76126
// Management of the installation is a one-off operation. The state cannot be updated through a read operation.
77127
// This read function will only invalidate the state (forcing recreation) if the installation has been deleted.
78128
func ResourceInstallationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
79-
provider := meta.(*common.Client)
80-
tempClient := SMAPI.NewClient(provider.SMAPIURL, d.Get("sm_access_token").(string), nil)
129+
apiURL := strings.Split(d.Id(), ";")[0]
130+
tempClient := SMAPI.NewClient(apiURL, d.Get("sm_access_token").(string), nil)
81131
if err := tempClient.ValidateToken(ctx); err != nil {
82132
log.Printf("[WARN] removing SM installation from state because it is no longer valid")
83133
d.SetId("")
@@ -87,8 +137,8 @@ func ResourceInstallationRead(ctx context.Context, d *schema.ResourceData, meta
87137
}
88138

89139
func ResourceInstallationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
90-
provider := meta.(*common.Client)
91-
tempClient := SMAPI.NewClient(provider.SMAPIURL, d.Get("sm_access_token").(string), nil)
140+
apiURL := strings.Split(d.Id(), ";")[0]
141+
tempClient := SMAPI.NewClient(apiURL, d.Get("sm_access_token").(string), nil)
92142
if err := tempClient.DeleteToken(ctx); err != nil {
93143
return diag.FromErr(err)
94144
}

internal/resources/cloud/resource_synthetic_monitoring_installation_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func TestAccSyntheticMonitoringInstallation(t *testing.T) {
3030
Check: resource.ComposeTestCheckFunc(
3131
testAccStackCheckExists("grafana_cloud_stack.test", &stack),
3232
resource.TestCheckResourceAttrSet("grafana_synthetic_monitoring_installation.test", "sm_access_token"),
33+
resource.TestCheckResourceAttrSet("grafana_synthetic_monitoring_installation.test", "stack_sm_api_url"),
3334
),
3435
},
3536
// Test deletion
@@ -50,8 +51,6 @@ func testAccSyntheticMonitoringInstallation(stackSlug, apiKeyName string) string
5051
`
5152
resource "grafana_synthetic_monitoring_installation" "test" {
5253
stack_id = grafana_cloud_stack.test.id
53-
metrics_instance_id = grafana_cloud_stack.test.prometheus_user_id
54-
logs_instance_id = grafana_cloud_stack.test.logs_user_id
5554
metrics_publisher_key = grafana_cloud_api_key.test.key
5655
}
5756
`

0 commit comments

Comments
 (0)