diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8407f6d..42f8db6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,12 +16,6 @@ jobs:
ruby: ['2.7', '3.0', '3.1']
gemfile:
- rails_7
- - rails_master
- exclude:
- - gemfile: rails_master
- ruby: 2.7
- - gemfile: rails_master
- ruby: 3.0
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile
BUNDLE_PATH_RELATIVE_TO_CWD: true
diff --git a/app/models/pager_tree/integrations/cloudflare/v3.rb b/app/models/pager_tree/integrations/cloudflare/v3.rb
new file mode 100644
index 0000000..a08f5f3
--- /dev/null
+++ b/app/models/pager_tree/integrations/cloudflare/v3.rb
@@ -0,0 +1,75 @@
+module PagerTree::Integrations
+ class Cloudflare::V3 < Integration
+ OPTIONS = [
+ {key: :webhook_secret, type: :string, default: nil}
+ ]
+ store_accessor :options, *OPTIONS.map { |x| x[:key] }.map(&:to_s), prefix: "option"
+
+ after_initialize do
+ self.option_webhook_secret ||= nil
+ end
+
+ def adapter_should_block_incoming?(request)
+ self.option_webhook_secret.present? && (request.headers["cf-webhook-auth"] != self.option_webhook_secret)
+ end
+
+ def adapter_supports_incoming?
+ true
+ end
+
+ def adapter_supports_outgoing?
+ false
+ end
+
+ def adapter_incoming_can_defer?
+ true
+ end
+
+ def adapter_thirdparty_id
+ @adapter_thirdparty_id ||= SecureRandom.uuid
+ end
+
+ def adapter_action
+ :create
+ end
+
+ def adapter_process_create
+ Alert.new(
+ title: _title,
+ description: _description,
+ thirdparty_id: adapter_thirdparty_id,
+ dedup_keys: [],
+ additional_data: _additional_datums
+ )
+ end
+
+ private
+
+ def _title
+ adapter_incoming_request_params.dig("text")&.truncate(70) || "CF Event #{adapter_incoming_request_params.dig("text")&.titleize}"
+ end
+
+ def _description
+ ActionController::Base.helpers.simple_format(adapter_incoming_request_params.dig("text"))
+ end
+
+ def _additional_datums
+ timestamp = begin
+ Time.at(adapter_incoming_request_params.dig("ts")).utc.to_datetime
+ rescue
+ nil
+ end
+
+ datums = []
+ datums << AdditionalDatum.new(format: "text", label: "Account Name", value: adapter_incoming_request_params.dig("data", "account_name"))
+ datums << AdditionalDatum.new(format: "text", label: "Zone Name", value: adapter_incoming_request_params.dig("data", "zone_name"))
+ datums << AdditionalDatum.new(format: "link", label: "Dashboard Link", value: adapter_incoming_request_params.dig("data", "dashboard_link"))
+ datums << AdditionalDatum.new(format: "datetime", label: "Timestamp", value: timestamp)
+ datums << AdditionalDatum.new(format: "text", label: "Alert Type", value: adapter_incoming_request_params.dig("alert_type"))
+ datums << AdditionalDatum.new(format: "text", label: "Account ID", value: adapter_incoming_request_params.dig("account_id"))
+ datums << AdditionalDatum.new(format: "text", label: "Policy ID", value: adapter_incoming_request_params.dig("policy_id"))
+
+ datums.filter { |datum| datum.value.present? }
+ end
+ end
+end
diff --git a/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb b/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb
new file mode 100644
index 0000000..7439b3a
--- /dev/null
+++ b/app/views/pager_tree/integrations/cloudflare/v3/_form_options.html.erb
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb b/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb
new file mode 100644
index 0000000..003399d
--- /dev/null
+++ b/app/views/pager_tree/integrations/cloudflare/v3/_show_options.html.erb
@@ -0,0 +1,12 @@
+
+
+ <%= t("activerecord.attributes.pager_tree/integrations/cloudflare/v3.option_webhook_secret") %>
+
+
+
+
+ <%= mask integration.option_webhook_secret %>
+
+
+
+
diff --git a/config/locales/en.yml b/config/locales/en.yml
index c9500b2..c3fcf23 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -18,6 +18,10 @@ en:
option_alert_handoff_hint_html: "Send when the alert has been handed off"
option_template_hint_html: "A handlebars template describing the body that should be posted. See docs for details."
option_send_linked_hint_html: "Send linked data (source, source_log, user, team)"
+ cloudflare:
+ v3:
+ form_options:
+ option_webhook_secret_hint_html: "Cloudflare Webhook Secret"
email:
v3:
form_options:
@@ -133,6 +137,8 @@ en:
option_outgoing_rules: "Outgoing Rules"
"pager_tree/integrations/apex_ping/v3":
option_api_key: "API Key"
+ "pager_tree/integrations/cloudflare/v3":
+ option_webhook_secret: "Webhook Secret"
"pager_tree/integrations/email/v3":
option_allow_spam: "Allow Spam"
option_dedup_threads: "Dedup Threads"
diff --git a/test/fixtures/pager_tree/integrations/integrations.yml b/test/fixtures/pager_tree/integrations/integrations.yml
index 9258338..5fad334 100644
--- a/test/fixtures/pager_tree/integrations/integrations.yml
+++ b/test/fixtures/pager_tree/integrations/integrations.yml
@@ -20,6 +20,11 @@ azure_monitor_v3:
type: "PagerTree::Integrations::AzureMonitor::V3"
# options: no_options
+cloudflare_v3:
+ type: "PagerTree::Integrations::Cloudflare::V3"
+ options:
+ webhook_secret: "abc123"
+
cronitor_v3:
type: "PagerTree::Integrations::Cronitor::V3"
# options: no_options
diff --git a/test/models/pager_tree/integrations/cloudflare/v3_test.rb b/test/models/pager_tree/integrations/cloudflare/v3_test.rb
new file mode 100644
index 0000000..259e77d
--- /dev/null
+++ b/test/models/pager_tree/integrations/cloudflare/v3_test.rb
@@ -0,0 +1,74 @@
+require "test_helper"
+
+module PagerTree::Integrations
+ class Cloudflare::V3Test < ActiveSupport::TestCase
+ include Integrateable
+
+ setup do
+ @integration = pager_tree_integrations_integrations(:cloudflare_v3)
+
+ @create_request = {
+ name: "Testing Webhook",
+ text: "Requests to the following zone(s) have been failing for at least 5 minutes: zone-name",
+ data: {
+ unreachable_zones: [
+ {
+ zone_name: "zone-name",
+ host: ""
+ }
+ ]
+ },
+ ts: 1710782148,
+ account_id: "abc123",
+ policy_id: "def456",
+ alert_type: "real_origin_monitoring"
+ }.with_indifferent_access
+ end
+
+ test "sanity" do
+ assert @integration.adapter_supports_incoming?
+ assert @integration.adapter_incoming_can_defer?
+ assert_not @integration.adapter_supports_outgoing?
+ assert @integration.adapter_show_alerts?
+ assert @integration.adapter_show_logs?
+ assert_not @integration.adapter_show_outgoing_webhook_delivery?
+ end
+
+ test "adapter_actions" do
+ @integration.adapter_incoming_request_params = @create_request
+ assert_equal :create, @integration.adapter_action
+ end
+
+ test "adapter_thirdparty_id" do
+ @integration.adapter_incoming_request_params = @create_request
+ assert @integration.adapter_thirdparty_id.present?
+ end
+
+ test "adapter_process_create" do
+ @integration.adapter_incoming_request_params = @create_request
+
+ true_alert = Alert.new(
+ title: "Requests to the following zone(s) have been failing for at least 5 ...",
+ description: ActionController::Base.helpers.simple_format(@create_request["text"]),
+ urgency: nil,
+ thirdparty_id: @integration.adapter_thirdparty_id,
+ dedup_keys: [],
+ additional_data: [
+ AdditionalDatum.new(format: "datetime", label: "Timestamp", value: Time.at(1710782148).utc.to_datetime),
+ AdditionalDatum.new(format: "text", label: "Alert Type", value: "real_origin_monitoring"),
+ AdditionalDatum.new(format: "text", label: "Account ID", value: "abc123"),
+ AdditionalDatum.new(format: "text", label: "Policy ID", value: "def456")
+ ]
+ )
+
+ assert_equal true_alert.to_json, @integration.adapter_process_create.to_json
+ end
+
+ test "blocking_incoming" do
+ @blocked_request = @create_request.deep_dup
+ @integration.option_webhook_secret = "abc123"
+ assert @integration.adapter_should_block_incoming?(OpenStruct.new({headers: {"cf-webhook-auth" => ""}}))
+ assert_not @integration.adapter_should_block_incoming?(OpenStruct.new({headers: {"cf-webhook-auth" => "abc123"}}))
+ end
+ end
+end