Skip to content

Commit 827beb4

Browse files
authored
Support Stackdriver v2 Prometheus metrics integration (#410)
Close #393
1 parent 63aff73 commit 827beb4

15 files changed

+3257
-7
lines changed

cloudamqp/resource_cloudamqp_integration_metric_prometheus.go

Lines changed: 153 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package cloudamqp
22

33
import (
44
"context"
5+
"encoding/base64"
6+
"encoding/json"
57
"fmt"
68
"strconv"
79
"strings"
@@ -33,7 +35,7 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
3335
Type: schema.TypeSet,
3436
Optional: true,
3537
MaxItems: 1,
36-
ConflictsWith: []string{"datadog_v3", "azure_monitor", "splunk_v2", "dynatrace", "cloudwatch_v3"},
38+
ConflictsWith: []string{"datadog_v3", "azure_monitor", "splunk_v2", "dynatrace", "cloudwatch_v3", "stackdriver_v2"},
3739
Elem: &schema.Resource{
3840
Schema: map[string]*schema.Schema{
3941
"api_key": {
@@ -53,7 +55,7 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
5355
Type: schema.TypeSet,
5456
Optional: true,
5557
MaxItems: 1,
56-
ConflictsWith: []string{"newrelic_v3", "azure_monitor", "splunk_v2", "dynatrace", "cloudwatch_v3"},
58+
ConflictsWith: []string{"newrelic_v3", "azure_monitor", "splunk_v2", "dynatrace", "cloudwatch_v3", "stackdriver_v2"},
5759
Elem: &schema.Resource{
5860
Schema: map[string]*schema.Schema{
5961
"api_key": {
@@ -79,7 +81,7 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
7981
Type: schema.TypeSet,
8082
Optional: true,
8183
MaxItems: 1,
82-
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "splunk_v2", "dynatrace", "cloudwatch_v3"},
84+
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "splunk_v2", "dynatrace", "cloudwatch_v3", "stackdriver_v2"},
8385
Elem: &schema.Resource{
8486
Schema: map[string]*schema.Schema{
8587
"connection_string": {
@@ -95,7 +97,7 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
9597
Type: schema.TypeSet,
9698
Optional: true,
9799
MaxItems: 1,
98-
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "dynatrace", "cloudwatch_v3"},
100+
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "dynatrace", "cloudwatch_v3", "stackdriver_v2"},
99101
Elem: &schema.Resource{
100102
Schema: map[string]*schema.Schema{
101103
"token": {
@@ -121,7 +123,7 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
121123
Type: schema.TypeSet,
122124
Optional: true,
123125
MaxItems: 1,
124-
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "splunk_v2", "cloudwatch_v3"},
126+
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "splunk_v2", "cloudwatch_v3", "stackdriver_v2"},
125127
Elem: &schema.Resource{
126128
Schema: map[string]*schema.Schema{
127129
"environment_id": {
@@ -147,7 +149,7 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
147149
Type: schema.TypeSet,
148150
Optional: true,
149151
MaxItems: 1,
150-
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "splunk_v2", "dynatrace"},
152+
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "splunk_v2", "dynatrace", "stackdriver_v2"},
151153
Elem: &schema.Resource{
152154
Schema: map[string]*schema.Schema{
153155
"iam_role": {
@@ -173,6 +175,69 @@ func resourceIntegrationMetricPrometheus() *schema.Resource {
173175
},
174176
},
175177
},
178+
"stackdriver_v2": {
179+
Type: schema.TypeList,
180+
Optional: true,
181+
MaxItems: 1,
182+
ConflictsWith: []string{"newrelic_v3", "datadog_v3", "azure_monitor", "splunk_v2", "dynatrace", "cloudwatch_v3"},
183+
Elem: &schema.Resource{
184+
Schema: map[string]*schema.Schema{
185+
"credentials_file": {
186+
Type: schema.TypeString,
187+
Required: true,
188+
Sensitive: true,
189+
Description: "Base64-encoded Google service account key JSON file",
190+
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
191+
// Only suppress for existing resources
192+
if d.Id() == "" {
193+
return false
194+
}
195+
196+
if stackdriver := d.Get("stackdriver_v2").([]any); len(stackdriver) > 0 {
197+
config := stackdriver[0].(map[string]any)
198+
newCredentials, err := extractStackdriverCredentials(new)
199+
if err != nil {
200+
return false
201+
}
202+
// Suppress diff if new credentials match current state
203+
return newCredentials["project_id"] == config["project_id"] &&
204+
newCredentials["client_email"] == config["client_email"] &&
205+
newCredentials["private_key_id"] == config["private_key_id"] &&
206+
newCredentials["private_key"] == config["private_key"]
207+
}
208+
return false
209+
},
210+
},
211+
"project_id": {
212+
Type: schema.TypeString,
213+
Computed: true,
214+
Description: "Google Cloud project ID (computed from credentials file)",
215+
},
216+
"client_email": {
217+
Type: schema.TypeString,
218+
Computed: true,
219+
Description: "Google service account client email (computed from credentials file)",
220+
},
221+
"private_key": {
222+
Type: schema.TypeString,
223+
Computed: true,
224+
Sensitive: true,
225+
Description: "Google service account private key (computed from credentials file)",
226+
},
227+
"private_key_id": {
228+
Type: schema.TypeString,
229+
Computed: true,
230+
Sensitive: true,
231+
Description: "Google service account private key ID (computed from credentials file)",
232+
},
233+
"tags": {
234+
Type: schema.TypeString,
235+
Optional: true,
236+
Description: "tags. E.g. env=prod,service=web",
237+
},
238+
},
239+
},
240+
},
176241
},
177242
}
178243
}
@@ -230,6 +295,23 @@ func resourceIntegrationMetricPrometheusCreate(ctx context.Context, d *schema.Re
230295
if tags := cloudwatchConfig["tags"]; tags != nil && tags != "" {
231296
params["tags"] = tags
232297
}
298+
} else if stackdriverList := d.Get("stackdriver_v2").([]any); len(stackdriverList) > 0 {
299+
intName = "stackdriver_v2"
300+
stackdriverConfig := stackdriverList[0].(map[string]any)
301+
credentials := stackdriverConfig["credentials_file"].(string)
302+
303+
extractedCredentials, err := extractStackdriverCredentials(credentials)
304+
if err != nil {
305+
return diag.FromErr(err)
306+
}
307+
308+
for key, value := range extractedCredentials {
309+
params[key] = value
310+
}
311+
312+
if tags := stackdriverConfig["tags"]; tags != nil && tags != "" {
313+
params["tags"] = tags
314+
}
233315
}
234316

235317
if intName == "" {
@@ -247,6 +329,32 @@ func resourceIntegrationMetricPrometheusCreate(ctx context.Context, d *schema.Re
247329
return resourceIntegrationMetricPrometheusRead(ctx, d, meta)
248330
}
249331

332+
func extractStackdriverCredentials(credentials string) (map[string]string, error) {
333+
decoded, err := base64.StdEncoding.DecodeString(credentials)
334+
if err != nil {
335+
return nil, fmt.Errorf("failed to decode stackdriver credentials: %s", err)
336+
}
337+
338+
var jsonMap map[string]any
339+
if err := json.Unmarshal(decoded, &jsonMap); err != nil {
340+
return nil, fmt.Errorf("failed to parse stackdriver credentials JSON: %s", err)
341+
}
342+
343+
requiredFields := []string{"client_email", "private_key_id", "private_key", "project_id"}
344+
for _, field := range requiredFields {
345+
if jsonMap[field] == nil || jsonMap[field] == "" {
346+
return nil, fmt.Errorf("required field '%s' is missing from credentials JSON", field)
347+
}
348+
}
349+
350+
return map[string]string{
351+
"client_email": jsonMap["client_email"].(string),
352+
"private_key_id": jsonMap["private_key_id"].(string),
353+
"private_key": jsonMap["private_key"].(string),
354+
"project_id": jsonMap["project_id"].(string),
355+
}, nil
356+
}
357+
250358
func resourceIntegrationMetricPrometheusRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
251359
if strings.Contains(d.Id(), ",") {
252360
tflog.Info(ctx, fmt.Sprintf("import resource with identifier: %s", d.Id()))
@@ -279,6 +387,7 @@ func resourceIntegrationMetricPrometheusRead(ctx context.Context, d *schema.Reso
279387
d.Set("splunk_v2", nil)
280388
d.Set("dynatrace", nil)
281389
d.Set("cloudwatch_v3", nil)
390+
d.Set("stackdriver_v2", nil)
282391

283392
name := strings.ToLower(data["type"].(string))
284393
if name == "newrelic_v3" {
@@ -359,6 +468,28 @@ func resourceIntegrationMetricPrometheusRead(ctx context.Context, d *schema.Reso
359468
if err := d.Set("cloudwatch_v3", cloudwatchV3); err != nil {
360469
return diag.Errorf("error setting cloudwatch_v3 for resource %s: %s", d.Id(), err)
361470
}
471+
} else if name == "stackdriver_v2" {
472+
stackdriverV2 := []map[string]any{{}}
473+
474+
if project_id, ok := data["project_id"]; ok {
475+
stackdriverV2[0]["project_id"] = project_id
476+
}
477+
if client_email, ok := data["client_email"]; ok {
478+
stackdriverV2[0]["client_email"] = client_email
479+
}
480+
if private_key, ok := data["private_key"]; ok {
481+
stackdriverV2[0]["private_key"] = private_key
482+
}
483+
if private_key_id, ok := data["private_key_id"]; ok {
484+
stackdriverV2[0]["private_key_id"] = private_key_id
485+
}
486+
if tags, ok := data["tags"]; ok {
487+
stackdriverV2[0]["tags"] = tags
488+
}
489+
490+
if err := d.Set("stackdriver_v2", stackdriverV2); err != nil {
491+
return diag.Errorf("error setting stackdriver_v2 for resource %s: %s", d.Id(), err)
492+
}
362493
}
363494

364495
return nil
@@ -410,6 +541,22 @@ func resourceIntegrationMetricPrometheusUpdate(ctx context.Context, d *schema.Re
410541
if tags := cloudwatchConfig["tags"]; tags != nil && tags != "" {
411542
params["tags"] = tags
412543
}
544+
} else if stackdriverList := d.Get("stackdriver_v2").([]interface{}); len(stackdriverList) > 0 {
545+
stackdriverConfig := stackdriverList[0].(map[string]any)
546+
547+
credentials := stackdriverConfig["credentials_file"].(string)
548+
extractedCreds, err := extractStackdriverCredentials(credentials)
549+
if err != nil {
550+
return diag.FromErr(err)
551+
}
552+
553+
for key, value := range extractedCreds {
554+
params[key] = value
555+
}
556+
557+
if tags := stackdriverConfig["tags"]; tags != nil && tags != "" {
558+
params["tags"] = tags
559+
}
413560
}
414561

415562
err := api.UpdateIntegration(ctx, d.Get("instance_id").(int), "metrics", d.Id(), params)

0 commit comments

Comments
 (0)