From fa660e546d2bdface914c6229f4537e9ad37c133 Mon Sep 17 00:00:00 2001 From: Rambatino Date: Wed, 24 Jul 2024 19:25:16 +0100 Subject: [PATCH 1/3] feature(custom_webhook): added custom webhook notifier support --- axiom/provider_test.go | 9 ++- axiom/resource_notifier.go | 36 ++++++++- axiom/resource_notifier_test.go | 126 ++++++++++++++++++++++++++++++++ docs/data-sources/notifier.md | 10 +++ go.mod | 5 +- go.sum | 9 ++- 6 files changed, 187 insertions(+), 8 deletions(-) create mode 100644 axiom/resource_notifier_test.go diff --git a/axiom/provider_test.go b/axiom/provider_test.go index adc9d54..13cd17b 100644 --- a/axiom/provider_test.go +++ b/axiom/provider_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" ax "github.com/axiomhq/axiom-go/axiom" ) @@ -180,9 +181,11 @@ func testAccCheckResourcesCreatesCorrectValues(client *ax.Client, resourceName, return fmt.Errorf("property %s not found in Terraform state", tfKey) } - actualValue, found := actualMap[apiKey] - if !found { - return fmt.Errorf("property %s not found in API response", apiKey) + // Use gjson to get the value using the dot notation path + actualValue := gjson.GetBytes(actualJSON, apiKey) + + if !actualValue.Exists() { + return fmt.Errorf("property %s not found in API response: %s", apiKey, string(actualJSON)) } if fmt.Sprintf("%v", actualValue) != stateValue { diff --git a/axiom/resource_notifier.go b/axiom/resource_notifier.go index 3ac148d..ff59b2a 100644 --- a/axiom/resource_notifier.go +++ b/axiom/resource_notifier.go @@ -48,6 +48,7 @@ type NotifierProperties struct { Pagerduty *PagerDutyConfig `tfsdk:"pagerduty"` Slack *SlackConfig `tfsdk:"slack"` Webhook *WebhookConfig `tfsdk:"webhook"` + CustomWebhook *CustomWebhookConfig `tfsdk:"custom_webhook"` } type SlackConfig struct { @@ -60,7 +61,7 @@ type DiscordConfig struct { } type DiscordWebhookConfig struct { - DiscordWebhookURL types.String `tfsdk:"Discord_webhook_url"` + DiscordWebhookURL types.String `tfsdk:"discord_webhook_url"` } type EmailConfig struct { @@ -81,6 +82,12 @@ type WebhookConfig struct { URL types.String `tfsdk:"url"` } +type CustomWebhookConfig struct { + URL types.String `tfsdk:"url"` + Headers map[types.String]types.String `tfsdk:"headers"` + Body types.String `tfsdk:"body"` +} + func (r *NotifierResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_notifier" } @@ -120,6 +127,7 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("opsgenie"), path.MatchRelative().AtParent().AtName("pagerduty"), path.MatchRelative().AtParent().AtName("webhook"), + path.MatchRelative().AtParent().AtName("custom_webhook"), ), }, }, @@ -143,6 +151,7 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("opsgenie"), path.MatchRelative().AtParent().AtName("pagerduty"), path.MatchRelative().AtParent().AtName("webhook"), + path.MatchRelative().AtParent().AtName("custom_webhook"), ), }, }, @@ -162,6 +171,7 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("opsgenie"), path.MatchRelative().AtParent().AtName("pagerduty"), path.MatchRelative().AtParent().AtName("webhook"), + path.MatchRelative().AtParent().AtName("custom_webhook"), ), }, }, @@ -182,6 +192,7 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("opsgenie"), path.MatchRelative().AtParent().AtName("pagerduty"), path.MatchRelative().AtParent().AtName("webhook"), + path.MatchRelative().AtParent().AtName("custom_webhook"), ), }, }, @@ -205,6 +216,7 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("email"), path.MatchRelative().AtParent().AtName("pagerduty"), path.MatchRelative().AtParent().AtName("webhook"), + path.MatchRelative().AtParent().AtName("custom_webhook"), ), }, }, @@ -228,6 +240,7 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("email"), path.MatchRelative().AtParent().AtName("opsgenie"), path.MatchRelative().AtParent().AtName("webhook"), + path.MatchRelative().AtParent().AtName("custom_webhook"), ), }, }, @@ -247,6 +260,27 @@ func (r *NotifierResource) Schema(_ context.Context, _ resource.SchemaRequest, r path.MatchRelative().AtParent().AtName("email"), path.MatchRelative().AtParent().AtName("opsgenie"), path.MatchRelative().AtParent().AtName("pagerduty"), + path.MatchRelative().AtParent().AtName("custom_webhook"), + ), + }, + }, + "custom_webhook": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "url": schema.StringAttribute{ + MarkdownDescription: "The webhook URL", + Required: true, + }, + }, + Optional: true, + Validators: []validator.Object{ + objectvalidator.ExactlyOneOf( + path.MatchRelative().AtParent().AtName("slack"), + path.MatchRelative().AtParent().AtName("discord"), + path.MatchRelative().AtParent().AtName("discord_webhook"), + path.MatchRelative().AtParent().AtName("email"), + path.MatchRelative().AtParent().AtName("opsgenie"), + path.MatchRelative().AtParent().AtName("pagerduty"), + path.MatchRelative().AtParent().AtName("webhook"), ), }, }, diff --git a/axiom/resource_notifier_test.go b/axiom/resource_notifier_test.go new file mode 100644 index 0000000..8d8864b --- /dev/null +++ b/axiom/resource_notifier_test.go @@ -0,0 +1,126 @@ +package axiom + +import ( + "os" + "testing" + + ax "github.com/axiomhq/axiom-go/axiom" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/assert" +) + +func TestNotifiers(t *testing.T) { + client, err := ax.NewClient() + assert.NoError(t, err) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "axiom": providerserver.NewProtocol6WithError(NewAxiomProvider()), + }, + CheckDestroy: testAccCheckAxiomResourcesDestroyed(client), + Steps: []resource.TestStep{ + { + Config: testConfigNotifier("slack", ` { + slack = { + slack_url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.slack", "properties.slack.slack_url", "properties.slack.slackUrl"), + ), + }, + { + Config: testConfigNotifier("discord", ` { + discord = { + discord_channel = "general" + discord_token = "fake_discord_token" + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.discord", "properties.discord.discord_channel", "properties.discord.discordChannel"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.discord", "properties.discord.discord_token", "properties.discord.discordToken"), + ), + }, + { + Config: testConfigNotifier("discord_webhook", ` { + discord_webhook = { + discord_webhook_url = "https://discord.com/api/webhooks/1234567890/abcdefghijklmnopqrstuvwxyz" + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.discord_webhook", "properties.discord_webhook.discord_webhook_url", "properties.discordWebhook.discordWebhookUrl"), + ), + }, + { + Config: testConfigNotifier("email", ` { + email = { + emails = ["test@example.com"] + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.email", "properties.email.emails.0", "properties.email.emails.0"), + ), + }, + { + Config: testConfigNotifier("opsgenie", ` { + opsgenie = { + api_key = "fake_opsgenie_api_key" + is_eu = true + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.opsgenie", "properties.opsgenie.api_key", "properties.opsgenie.apiKey"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.opsgenie", "properties.opsgenie.is_eu", "properties.opsgenie.isEU"), + ), + }, + { + Config: testConfigNotifier("pagerduty", ` { + pagerduty = { + routing_key = "fake_routing_key" + token = "fake_pagerduty_token" + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.pagerduty", "properties.pagerduty.routing_key", "properties.pagerduty.routingKey"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.pagerduty", "properties.pagerduty.token", "properties.pagerduty.token"), + ), + }, + { + Config: testConfigNotifier("webhook", ` { + webhook = { + url = "https://example.com/webhook" + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.webhook", "properties.webhook.url", "properties.webhook.url"), + ), + }, + { + Config: testConfigNotifier("custom_webhook", ` { + custom_webhook = { + url = "https://example.com/custom_webhook" + headers = { + "Authorization" = "Bearer token" + "Content-Type" = "application/json" + } + body = "{\"message\": \"test\"}" + } }`), + Check: resource.ComposeTestCheckFunc( + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.url", "properties.customWebhook.url"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.headers.Authorization", "properties.customWebhook.headers.Authorization"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.headers.Content-Type", "properties.customWebhook.headers.Content-Type"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.body", "properties.customWebhook.body"), + ), + }, + }, + }) +} + +func testConfigNotifier(notifierType, notifierConfig string) string { + return ` + provider "axiom" { + api_token = "` + os.Getenv("AXIOM_TOKEN") + `" + base_url = "` + os.Getenv("AXIOM_URL") + `" + } + + resource "axiom_notifier" "` + notifierType + `" { + name = "` + notifierType + `" + properties = ` + notifierConfig + ` + } + ` +} diff --git a/docs/data-sources/notifier.md b/docs/data-sources/notifier.md index 1ced73b..51f9fbd 100644 --- a/docs/data-sources/notifier.md +++ b/docs/data-sources/notifier.md @@ -36,6 +36,7 @@ Read-Only: - `pagerduty` (Attributes) (see [below for nested schema](#nestedatt--properties--pagerduty)) - `slack` (Attributes) (see [below for nested schema](#nestedatt--properties--slack)) - `webhook` (Attributes) (see [below for nested schema](#nestedatt--properties--webhook)) +- `custom_webhook` (Attributes) (see [below for nested schema](#nestedatt--properties--custom_webhook)) ### Nested Schema for `properties.discord` @@ -94,3 +95,12 @@ Read-Only: Read-Only: - `url` (String) The webhook URL + + +### Nested Schema for `properties.custom_webhook` + +Read-Only: + +- `url` (String) The custom webhook URL +- `headers` (Map of String) Headers to include in the webhook request +- `body` (String) The body of the webhook request diff --git a/go.mod b/go.mod index 8a37cbe..4fc1e73 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module terraform-provider-axiom-provider go 1.21 require ( - github.com/axiomhq/axiom-go v0.19.2-0.20240709110117-caafaa74e293 + github.com/axiomhq/axiom-go v0.20.0 github.com/hashicorp/terraform-plugin-docs v0.18.0 github.com/hashicorp/terraform-plugin-framework v1.9.0 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 @@ -11,6 +11,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 github.com/stretchr/testify v1.9.0 + github.com/tidwall/gjson v1.17.1 ) require ( @@ -71,6 +72,8 @@ require ( github.com/russross/blackfriday v1.6.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/spf13/cast v1.6.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index cbf7b7d..9632019 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/axiomhq/axiom-go v0.19.2-0.20240709110117-caafaa74e293 h1:bt1BxWjyCki3Wn48b8sbsgsuyp/5mPrfg2HkGMiR4Lc= github.com/axiomhq/axiom-go v0.19.2-0.20240709110117-caafaa74e293/go.mod h1:Xg8idGSaWD6F1oMFtjIZhblOb7CKhJuERh5IISl05mE= +github.com/axiomhq/axiom-go v0.20.0 h1:E/QufPTxqu7+AORsY8nmtWSEWHwBj3qQBUU83mgrm60= +github.com/axiomhq/axiom-go v0.20.0/go.mod h1:Xg8idGSaWD6F1oMFtjIZhblOb7CKhJuERh5IISl05mE= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= @@ -210,12 +212,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= From 4a4a49e3b219e1dcea9a063814fe0b1da82fdb4e Mon Sep 17 00:00:00 2001 From: Rambatino Date: Fri, 26 Jul 2024 13:46:11 +0100 Subject: [PATCH 2/3] feat(monitors): support match events --- axiom/provider_test.go | 20 ++++++ axiom/resource_notifier.go | 40 ++++++++++- axiom/resource_notifier_test.go | 17 ++++- example/main.tf | 20 +++++- go.mod | 47 ++++++------- go.sum | 115 ++++++++++++++++---------------- 6 files changed, 170 insertions(+), 89 deletions(-) diff --git a/axiom/provider_test.go b/axiom/provider_test.go index 13cd17b..efa7cb3 100644 --- a/axiom/provider_test.go +++ b/axiom/provider_test.go @@ -236,6 +236,26 @@ resource "axiom_monitor" "test_monitor" { notify_by_group = false } +resource "axiom_monitor" "test_monitor_match_event" { + depends_on = [axiom_dataset.test, axiom_notifier.slack_test] + + name = "test event matching monitor" + description = "test_monitor updated" + apl_query = < Date: Fri, 26 Jul 2024 15:27:44 +0100 Subject: [PATCH 3/3] chore: bump go version in go.mod --- axiom/resource_notifier.go | 2 +- axiom/resource_notifier_test.go | 8 ++++---- go.mod | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/axiom/resource_notifier.go b/axiom/resource_notifier.go index a4da785..7fd29b2 100644 --- a/axiom/resource_notifier.go +++ b/axiom/resource_notifier.go @@ -461,7 +461,7 @@ func extractNotifier(ctx context.Context, plan NotifierResourceModel) (*axiom.No if diags.HasError() { return nil, diags } - notifier.Properties.CustomWebhook = &axiom.CustomWebhookConfig{ + notifier.Properties.CustomWebhook = &axiom.CustomWebhook{ URL: plan.Properties.CustomWebhook.URL.ValueString(), Headers: headers, Body: plan.Properties.CustomWebhook.Body.ValueString(), diff --git a/axiom/resource_notifier_test.go b/axiom/resource_notifier_test.go index 50f6711..7466b75 100644 --- a/axiom/resource_notifier_test.go +++ b/axiom/resource_notifier_test.go @@ -116,10 +116,10 @@ func TestNotifiers(t *testing.T) { EOF`+` } }`), Check: resource.ComposeTestCheckFunc( - testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.url", "properties.customWebhook.url"), - testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.headers.Authorization", "properties.customWebhook.headers.Authorization"), - testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.headers.Content-Type", "properties.customWebhook.headers.Content-Type"), - testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.body", "properties.customWebhook.body"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.url", "properties.customWebhook.URL"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.headers.Authorization", "properties.customWebhook.Headers.Authorization"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.headers.Content-Type", "properties.customWebhook.Headers.Content-Type"), + testAccCheckResourcesCreatesCorrectValues(client, "axiom_notifier.custom_webhook", "properties.custom_webhook.body", "properties.customWebhook.Body"), ), }, }, diff --git a/go.mod b/go.mod index 10b78ec..2584a99 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module terraform-provider-axiom-provider -go 1.22.3 +go 1.22.5 require ( github.com/axiomhq/axiom-go v0.20.0