Skip to content

Commit

Permalink
Add plan-phase validation for request_headers (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrHeinz authored Aug 8, 2024
1 parent 3f87a50 commit 0c6b66d
Show file tree
Hide file tree
Showing 15 changed files with 248 additions and 48 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ GOLANGCI_LINT := golangci-lint run --disable-all \
-E staticcheck \
-E typecheck \
-E unused
VERSION := 0.11.3
VERSION := 0.11.7
.PHONY: test build

help:
Expand Down
6 changes: 6 additions & 0 deletions examples/advanced/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ resource "betteruptime_monitor" "status" {
url = "https://example.com"
monitor_type = "status"
monitor_group_id = betteruptime_monitor_group.this.id
request_headers = [
{
"name" : "X-For-Status-Page",
"value" : "https://${betteruptime_status_page.this.subdomain}.betteruptime.com"
},
]
}

resource "betteruptime_monitor" "dns" {
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ terraform {
required_providers {
betteruptime = {
source = "BetterStackHQ/better-uptime"
version = ">= 0.8.0"
version = ">= 0.11.6"
}
}
}
13 changes: 9 additions & 4 deletions internal/provider/resource_aws_cloudwatch_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ func newAwsCloudWatchIntegrationResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/aws-cloudwatch-integrations/",
Schema: awsCloudWatchIntegrationSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/aws-cloudwatch-integrations/",
Schema: awsCloudWatchIntegrationSchema,
}
}

Expand Down Expand Up @@ -151,7 +152,9 @@ func awsCloudWatchIntegrationCreate(ctx context.Context, d *schema.ResourceData,
var in awsCloudWatchIntegration
for _, e := range awsCloudWatchIntegrationRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -192,7 +195,9 @@ func awsCloudWatchIntegrationUpdate(ctx context.Context, d *schema.ResourceData,
for _, e := range awsCloudWatchIntegrationRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down
13 changes: 9 additions & 4 deletions internal/provider/resource_azure_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ func newAzureIntegrationResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/azure-integrations/",
Schema: azureIntegrationSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/azure-integrations/",
Schema: azureIntegrationSchema,
}
}

Expand Down Expand Up @@ -151,7 +152,9 @@ func azureIntegrationCreate(ctx context.Context, d *schema.ResourceData, meta in
var in azureIntegration
for _, e := range azureIntegrationRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -192,7 +195,9 @@ func azureIntegrationUpdate(ctx context.Context, d *schema.ResourceData, meta in
for _, e := range azureIntegrationRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down
13 changes: 9 additions & 4 deletions internal/provider/resource_datadog_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ func newDatadogIntegrationResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/datadog-integrations/",
Schema: datadogIntegrationSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/datadog-integrations/",
Schema: datadogIntegrationSchema,
}
}

Expand Down Expand Up @@ -159,7 +160,9 @@ func datadogIntegrationCreate(ctx context.Context, d *schema.ResourceData, meta
var in datadogIntegration
for _, e := range datadogIntegrationRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -200,7 +203,9 @@ func datadogIntegrationUpdate(ctx context.Context, d *schema.ResourceData, meta
for _, e := range datadogIntegrationRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down
13 changes: 9 additions & 4 deletions internal/provider/resource_google_monitoring_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ func newGoogleMonitoringIntegrationResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/google-monitoring-integrations/",
Schema: googleMonitoringIntegrationSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/google-monitoring-integrations/",
Schema: googleMonitoringIntegrationSchema,
}
}

Expand Down Expand Up @@ -151,7 +152,9 @@ func googleMonitoringIntegrationCreate(ctx context.Context, d *schema.ResourceDa
var in googleMonitoringIntegration
for _, e := range googleMonitoringIntegrationRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -192,7 +195,9 @@ func googleMonitoringIntegrationUpdate(ctx context.Context, d *schema.ResourceDa
for _, e := range googleMonitoringIntegrationRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down
13 changes: 9 additions & 4 deletions internal/provider/resource_grafana_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ func newGrafanaIntegrationResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/grafana-integrations/",
Schema: grafanaIntegrationSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/grafana-integrations/",
Schema: grafanaIntegrationSchema,
}
}

Expand Down Expand Up @@ -151,7 +152,9 @@ func grafanaIntegrationCreate(ctx context.Context, d *schema.ResourceData, meta
var in grafanaIntegration
for _, e := range grafanaIntegrationRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -192,7 +195,9 @@ func grafanaIntegrationUpdate(ctx context.Context, d *schema.ResourceData, meta
for _, e := range grafanaIntegrationRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down
13 changes: 9 additions & 4 deletions internal/provider/resource_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ func newMetadataResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/metadata/",
Schema: metadataSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/metadata/",
Schema: metadataSchema,
}
}

Expand Down Expand Up @@ -115,7 +116,9 @@ func metadataCreate(ctx context.Context, d *schema.ResourceData, meta interface{
var in metadata
for _, e := range metadataRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -156,7 +159,9 @@ func metadataUpdate(ctx context.Context, d *schema.ResourceData, meta interface{
for _, e := range metadataRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down
69 changes: 64 additions & 5 deletions internal/provider/resource_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,9 @@ func newMonitorResource() *schema.Resource {
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Description: "https://betterstack.com/docs/uptime/api/monitors/",
Schema: monitorSchema,
CustomizeDiff: validateRequestHeaders,
Description: "https://betterstack.com/docs/uptime/api/monitors/",
Schema: monitorSchema,
}
}

Expand Down Expand Up @@ -511,7 +512,9 @@ func monitorCreate(ctx context.Context, d *schema.ResourceData, meta interface{}
var in monitor
for _, e := range monitorRef(&in) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand Down Expand Up @@ -552,7 +555,9 @@ func monitorUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}
for _, e := range monitorRef(&in) {
if d.HasChange(e.k) {
if e.k == "request_headers" {
loadRequestHeaders(d, e.v.(**[]map[string]interface{}))
if err := loadRequestHeaders(d, e.v.(**[]map[string]interface{})); err != nil {
return diag.FromErr(err)
}
} else {
load(d, e.k, e.v)
}
Expand All @@ -566,12 +571,64 @@ func monitorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}
return resourceDelete(ctx, meta, fmt.Sprintf("/api/v2/monitors/%s", url.PathEscape(d.Id())))
}

func loadRequestHeaders(d *schema.ResourceData, receiver **[]map[string]interface{}) {
func validateRequestHeaders(ctx context.Context, diff *schema.ResourceDiff, v interface{}) error {
if headers, ok := diff.GetOk("request_headers"); ok {
for _, header := range headers.([]interface{}) {
headerMap := header.(map[string]interface{})
if err := validateRequestHeader(headerMap); err != nil {
return fmt.Errorf("Invalid request header %v: %v", headerMap, err)
}
}
}
return nil
}

func validateRequestHeader(header map[string]interface{}) error {
if len(header) == 0 {
// Headers with calculated fields that are not known at the time will be passed as empty maps, ignore them
return nil
}

name, nameOk := header["name"].(string)
value, valueOk := header["value"].(string)

if !nameOk || name == "" {
return fmt.Errorf("must contain 'name' key with a non-empty string value")
}

if !valueOk || value == "" {
return fmt.Errorf("must contain 'value' key with a non-empty string value")
}

if len(header) != 2 {
return fmt.Errorf("must only contain 'name' and 'value' keys")
}

return nil
}

func loadRequestHeaders(d *schema.ResourceData, receiver **[]map[string]interface{}) error {
x := receiver
v := d.Get("request_headers")
var t []map[string]interface{}
for _, v := range v.([]interface{}) {
header := v.(map[string]interface{})

// Validation at apply time, empty map is considered invalid (fields should be known at this point)
if len(header) == 0 {
return fmt.Errorf("Invalid request header %v: map cannot be empty", header)
}
// Headers can have ID at apply time, temporarily remove it before validation and reattach it afterwards
id, idPresent := header["id"]
delete(header, "id")
err := validateRequestHeader(header)
if idPresent {
header["id"] = id
}
if err != nil {
return fmt.Errorf("Invalid request header %v: %v", header, err)
}

newHeader := map[string]interface{}{"name": header["name"], "value": header["value"]}
t = append(t, newHeader)
}
Expand All @@ -598,6 +655,8 @@ func loadRequestHeaders(d *schema.ResourceData, receiver **[]map[string]interfac
}

*x = &t

return nil
}

func findRequestHeader(headers *[]map[string]interface{}, header *map[string]interface{}) *map[string]interface{} {
Expand Down
Loading

0 comments on commit 0c6b66d

Please sign in to comment.