diff --git a/docs/resources/audit_trail_webhook.md b/docs/resources/audit_trail_webhook.md index 4c5fa483..b64c991a 100644 --- a/docs/resources/audit_trail_webhook.md +++ b/docs/resources/audit_trail_webhook.md @@ -27,10 +27,11 @@ resource "spacelift_audit_trail_webhook" "example" { - `enabled` (Boolean) `enabled` determines whether the webhook is enabled. If it is not, Spacelift will not send any requests to the endpoint. - `endpoint` (String) `endpoint` is the URL to which Spacelift will send POST requests about audit events. -- `secret` (String, Sensitive) `secret` is a secret that Spacelift will send with the request +- `secret` (String, Sensitive) `secret` is a secret that Spacelift will send with the request. ### Optional +- `custom_headers` (Map of String) `custom_headers` is a Map of key-value strings, that will be passed as headers with audit trail requests. - `include_runs` (Boolean) `include_runs` determines whether the webhook should include information about the run that triggered the event. ### Read-Only diff --git a/spacelift/internal/structs/audit_trail_webhook.go b/spacelift/internal/structs/audit_trail_webhook.go index 181eaf0e..5dda5921 100644 --- a/spacelift/internal/structs/audit_trail_webhook.go +++ b/spacelift/internal/structs/audit_trail_webhook.go @@ -1,6 +1,12 @@ package structs type AuditTrailWebhook struct { + AuditTrailWebhookRead + // CustomHeaders is used only for create/update, API doesn't return them back. + CustomHeaders StringMap `graphql:"customHeaders"` +} + +type AuditTrailWebhookRead struct { Enabled bool `graphql:"enabled"` Endpoint string `graphql:"endpoint"` IncludeRuns bool `graphql:"includeRuns"` diff --git a/spacelift/internal/structs/audit_trail_webhook_input.go b/spacelift/internal/structs/audit_trail_webhook_input.go index f5c2b6b6..759c1553 100644 --- a/spacelift/internal/structs/audit_trail_webhook_input.go +++ b/spacelift/internal/structs/audit_trail_webhook_input.go @@ -3,8 +3,21 @@ package structs import "github.com/shurcooL/graphql" type AuditTrailWebhookInput struct { - Enabled graphql.Boolean `json:"enabled"` - Endpoint graphql.String `json:"endpoint"` - IncludeRuns graphql.Boolean `json:"includeRuns"` - Secret graphql.String `json:"secret"` + Enabled graphql.Boolean `json:"enabled"` + Endpoint graphql.String `json:"endpoint"` + IncludeRuns graphql.Boolean `json:"includeRuns"` + Secret graphql.String `json:"secret"` + CustomHeaders *StringMap `json:"customHeaders"` +} + +type StringMap struct { + Entries []KeyValuePair `json:"entries"` +} + +func (m StringMap) ToStdMap() map[string]string { + mapped := make(map[string]string) + for _, kv := range m.Entries { + mapped[kv.Key] = kv.Value + } + return mapped } diff --git a/spacelift/resource_audit_trail_webhook.go b/spacelift/resource_audit_trail_webhook.go index 23523521..834e6faf 100644 --- a/spacelift/resource_audit_trail_webhook.go +++ b/spacelift/resource_audit_trail_webhook.go @@ -47,7 +47,12 @@ func resourceAuditTrailWebhook() *schema.Resource { Type: schema.TypeString, Required: true, Sensitive: true, - Description: "`secret` is a secret that Spacelift will send with the request", + Description: "`secret` is a secret that Spacelift will send with the request.", + }, + "custom_headers": { + Type: schema.TypeMap, + Optional: true, + Description: "`custom_headers` is a Map of key-value strings, that will be passed as headers with audit trail requests.", }, }, } @@ -55,14 +60,15 @@ func resourceAuditTrailWebhook() *schema.Resource { func resourceAuditTrailWebhookCreate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { var mutation struct { - AuditTrailWebhook *structs.AuditTrailWebhook `graphql:"auditTrailSetWebhook(input: $input)"` + AuditTrailWebhook *structs.AuditTrailWebhookRead `graphql:"auditTrailSetWebhook(input: $input)"` } variables := map[string]interface{}{ "input": structs.AuditTrailWebhookInput{ - Enabled: toBool(data.Get("enabled")), - Endpoint: toString(data.Get("endpoint")), - IncludeRuns: toBool(data.Get("include_runs")), - Secret: toString(data.Get("secret")), + Enabled: toBool(data.Get("enabled")), + Endpoint: toString(data.Get("endpoint")), + IncludeRuns: toBool(data.Get("include_runs")), + Secret: toString(data.Get("secret")), + CustomHeaders: toOptionalStringMap(data.Get("custom_headers")), }, } if err := i.(*internal.Client).Mutate(ctx, "AuditTrailWebhookCreate", &mutation, variables); err != nil { @@ -76,7 +82,7 @@ func resourceAuditTrailWebhookCreate(ctx context.Context, data *schema.ResourceD func resourceAuditTrailWebhookRead(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { var query struct { - AuditTrailWebhook *structs.AuditTrailWebhook `graphql:"auditTrailWebhook"` + AuditTrailWebhook *structs.AuditTrailWebhookRead `graphql:"auditTrailWebhook"` } if err := i.(*internal.Client).Query(ctx, "AuditTrailWebhookRead", &query, nil); err != nil { return diag.Errorf("could not query for audit trail webhook: %v", internal.FromSpaceliftError(err)) @@ -97,14 +103,15 @@ func resourceAuditTrailWebhookRead(ctx context.Context, data *schema.ResourceDat func resourceAuditTrailWebhookUpdate(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { var mutation struct { - AuditTrailWebhook *structs.AuditTrailWebhook `graphql:"auditTrailSetWebhook(input: $input)"` + AuditTrailWebhook *structs.AuditTrailWebhookRead `graphql:"auditTrailSetWebhook(input: $input)"` } variables := map[string]interface{}{ "input": structs.AuditTrailWebhookInput{ - Enabled: toBool(data.Get("enabled")), - Endpoint: toString(data.Get("endpoint")), - IncludeRuns: toBool(data.Get("include_runs")), - Secret: toString(data.Get("secret")), + Enabled: toBool(data.Get("enabled")), + Endpoint: toString(data.Get("endpoint")), + IncludeRuns: toBool(data.Get("include_runs")), + Secret: toString(data.Get("secret")), + CustomHeaders: toOptionalStringMap(data.Get("custom_headers")), }, } if err := i.(*internal.Client).Mutate(ctx, "AuditTrailWebhookUpdate", &mutation, variables); err != nil { @@ -116,7 +123,7 @@ func resourceAuditTrailWebhookUpdate(ctx context.Context, data *schema.ResourceD func resourceAuditTrailWebhookDelete(ctx context.Context, data *schema.ResourceData, i interface{}) diag.Diagnostics { var mutation struct { - AuditTrailWebhook *structs.AuditTrailWebhook `graphql:"auditTrailDeleteWebhook"` + AuditTrailWebhook *structs.AuditTrailWebhookRead `graphql:"auditTrailDeleteWebhook"` } if err := i.(*internal.Client).Mutate(ctx, "AuditTrailWebhookDelete", &mutation, nil); err != nil { return diag.Errorf("could not delete audit trail webhook: %v", internal.FromSpaceliftError(err)) diff --git a/spacelift/resource_audit_trail_webhook_test.go b/spacelift/resource_audit_trail_webhook_test.go index e840d6e0..a715d50e 100644 --- a/spacelift/resource_audit_trail_webhook_test.go +++ b/spacelift/resource_audit_trail_webhook_test.go @@ -10,7 +10,7 @@ import ( . "github.com/spacelift-io/terraform-provider-spacelift/spacelift/internal/testhelpers" ) -var auditTrailWebhookSimple = ` +const auditTrailWebhookSimple = ` resource "spacelift_audit_trail_webhook" "test" { enabled = true endpoint = "%s" @@ -19,6 +19,19 @@ resource "spacelift_audit_trail_webhook" "test" { } ` +const auditTrailWebhookCustomHeaders = ` +resource "spacelift_audit_trail_webhook" "test" { + enabled = true + endpoint = "%s" + include_runs = true + secret = "secret" + custom_headers = { + "X-Some-Header" = "some-value" + "X-Some-Header-2" = "some-value-2" + } +} +` + func Test_resourceAuditTrailWebhook(t *testing.T) { const resourceName = "spacelift_audit_trail_webhook.test" @@ -39,6 +52,19 @@ func Test_resourceAuditTrailWebhook(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: fmt.Sprintf(auditTrailWebhookCustomHeaders, "https://example.com"), + Check: Resource( + resourceName, + Attribute("enabled", Equals("true")), + Attribute("endpoint", Equals("https://example.com")), + Attribute("include_runs", Equals("true")), + Attribute("secret", Equals("secret")), + Attribute("custom_headers.%", Equals("2")), + Attribute("custom_headers.X-Some-Header", Equals("some-value")), + Attribute("custom_headers.X-Some-Header-2", Equals("some-value-2")), + ), + }, }) }) @@ -50,4 +76,5 @@ func Test_resourceAuditTrailWebhook(t *testing.T) { }, }) }) + } diff --git a/spacelift/type_conversions.go b/spacelift/type_conversions.go index 552e29a8..aae43514 100644 --- a/spacelift/type_conversions.go +++ b/spacelift/type_conversions.go @@ -3,6 +3,7 @@ package spacelift import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/shurcooL/graphql" + "github.com/spacelift-io/terraform-provider-spacelift/spacelift/internal/structs" ) func toBool(input interface{}) graphql.Boolean { @@ -25,6 +26,10 @@ func toString(input interface{}) graphql.String { return graphql.String(input.(string)) } +func toMap(input interface{}) map[string]interface{} { + return input.(map[string]interface{}) +} + func toOptionalInt(input interface{}) *graphql.Int { v := graphql.Int(input.(int)) return graphql.NewInt(v) @@ -45,3 +50,17 @@ func toOptionalStringList(input interface{}) *[]graphql.String { return nil } + +func toOptionalStringMap(input interface{}) *structs.StringMap { + var customHeaders structs.StringMap + for k, v := range toMap(input) { + customHeaders.Entries = append(customHeaders.Entries, structs.KeyValuePair{ + Key: k, + Value: v.(string), + }) + } + if len(customHeaders.Entries) == 0 { + return nil + } + return &customHeaders +}