Skip to content

Commit

Permalink
Feature/add custom headers to audit trail (#563)
Browse files Browse the repository at this point in the history
* Add custom headers for audit trail

* Update custom headers description

* Remove custom headers from read

* Fix typo in comments

Co-authored-by: Elie CHARRA <[email protected]>

* fix: typo in description

Co-authored-by: Adam Connelly <[email protected]>

* regen docs

---------

Co-authored-by: Elie CHARRA <[email protected]>
Co-authored-by: Adam Connelly <[email protected]>
  • Loading branch information
3 people authored Aug 14, 2024
1 parent dee0f60 commit 792c386
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 19 deletions.
3 changes: 2 additions & 1 deletion docs/resources/audit_trail_webhook.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions spacelift/internal/structs/audit_trail_webhook.go
Original file line number Diff line number Diff line change
@@ -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"`
Expand Down
21 changes: 17 additions & 4 deletions spacelift/internal/structs/audit_trail_webhook_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
33 changes: 20 additions & 13 deletions spacelift/resource_audit_trail_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,28 @@ 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.",
},
},
}
}

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 {
Expand All @@ -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))
Expand All @@ -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 {
Expand All @@ -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))
Expand Down
29 changes: 28 additions & 1 deletion spacelift/resource_audit_trail_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"

Expand All @@ -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")),
),
},
})
})

Expand All @@ -50,4 +76,5 @@ func Test_resourceAuditTrailWebhook(t *testing.T) {
},
})
})

}
19 changes: 19 additions & 0 deletions spacelift/type_conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
}

0 comments on commit 792c386

Please sign in to comment.