diff --git a/CHANGELOG.md b/CHANGELOG.md index 272c195..10cceea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Added map_v2_event_into_v1 method to Utils for all plugin classes to use. +- Added --map-v2-event-into-v1 runtime commandline option to base Handler and Mutator classes. +- Alternatively set envvar SENSU_MAP_V2_EVENT_INTO_V1=1 and handlers/mutators will automatically attempt to map 2.x event data. +- New cli option/envvar makes it possible to use sensu-plugin based handlers/mutators + with Sensu 2.0 events until they provide native 2.0 event support internally. +- Mapping function sets and checks for boolean event attribute 'v2_event_mapped_into_v1', + to prevent mapping from running multiple times in same pipeline. ## [2.6.0] - 2018-08-28 ### Fixed diff --git a/lib/sensu-handler.rb b/lib/sensu-handler.rb index 1ed0344..0a78486 100644 --- a/lib/sensu-handler.rb +++ b/lib/sensu-handler.rb @@ -9,6 +9,10 @@ module Sensu class Handler include Sensu::Plugin::Utils include Mixlib::CLI + option :map_v2_event_into_v1, + description: 'Enable 2.x to 1.4 event mapping. Alternatively set envvar SENSU_MAP_V2_EVENT_INTO_V1=1.', + boolean: true, + long: '--map-v2-event-into-v1' attr_accessor :argv @@ -73,6 +77,14 @@ def self.disable_autorun if @@autorun handler = @@autorun.new handler.read_event(STDIN) + + TRUTHY_VALUES = %w[1 t true yes y].freeze + automap = ENV['SENSU_MAP_V2_EVENT_INTO_V1'].to_s.downcase + + if handler.config[:map_v2_event_into_v1] || TRUTHY_VALUES.include?(automap) + new_event = handler.map_v2_event_into_v1 + handler.event = new_event + end handler.filter handler.handle end diff --git a/lib/sensu-mutator.rb b/lib/sensu-mutator.rb index ed72d6f..c62df52 100755 --- a/lib/sensu-mutator.rb +++ b/lib/sensu-mutator.rb @@ -34,6 +34,10 @@ module Sensu class Mutator include Sensu::Plugin::Utils include Mixlib::CLI + option :map_v2_event_into_v1, + description: 'Enable 2.x to 1.4 event mapping. Alternatively set envvar SENSU_MAP_V2_EVENT_INTO_V1=1.', + boolean: true, + long: '--map-v2-event-into-v1' attr_accessor :argv @@ -67,6 +71,15 @@ def self.disable_autorun return unless @@autorun mutator = @@autorun.new mutator.read_event(STDIN) + + TRUTHY_VALUES = %w[1 t true yes y].freeze + automap = ENV['SENSU_MAP_V2_EVENT_INTO_V1'].to_s.downcase + + if mutator.config[:map_v2_event_into_v1] || TRUTHY_VALUES.include?(automap) + new_event = mutator.map_v2_event_into_v1 + mutator.event = new_event + end + mutator.mutate mutator.dump end diff --git a/lib/sensu-plugin/utils.rb b/lib/sensu-plugin/utils.rb index caa4e80..7cf1985 100644 --- a/lib/sensu-plugin/utils.rb +++ b/lib/sensu-plugin/utils.rb @@ -23,6 +23,14 @@ def settings @settings ||= config_files.map { |f| load_config(f) }.reduce { |a, b| deep_merge(a, b) } end + def event + @event + end + + def event=(value) + @event = value + end + def read_event(file) @event = ::JSON.parse(file.read) @event['occurrences'] ||= 1 @@ -33,6 +41,92 @@ def read_event(file) exit 0 end + ## + # Helper method to convert Sensu 2.0 event into Sensu 1.4 event + # This is here to help keep Sensu Plugin community handlers working + # until they natively support 2.0 + # Takes 2.0 event json object as argument + # Returns event with 1.4 mapping included + # + # Note: + # The 1.4 mapping overwrites some attributes so the resulting event cannot + # be used in a 2.0 workflow. The top level boolean attribute "v2_event_mapped_into_v1" + # will be set to true as a hint to indicate this is a mapped event object. + # + ## + def map_v2_event_into_v1(orig_event = nil) + orig_event ||= @event + + # return orig_event if already mapped + return orig_event if orig_event['v2_event_mapped_into_v1'] + + # Deep copy of orig_event + event = Marshal.load(Marshal.dump(orig_event)) + + # Trigger mapping code if enity exists and client does not + client_missing = event['client'].nil? || event['client'].empty? + if event.key?('entity') && client_missing + ## + # create the client hash from the entity hash + ## + event['client'] = event['entity'] + + ## + # Fill in missing client attributes + ## + event['client']['name'] ||= event['entity']['id'] + event['client']['subscribers'] ||= event['entity']['subscriptions'] + + ## + # Fill in renamed check attributes expected in 1.4 event + # subscribers, source + ## + event['check']['subscribers'] ||= event['check']['subscriptions'] + event['check']['source'] ||= event['check']['proxy_entity_id'] + + ## + # Mimic 1.4 event action based on 2.0 event state + # action used in logs and fluentd plugins handlers + ## + action_state_mapping = { + 'flapping' => 'flapping', + 'passing' => 'resolve', + 'failing' => 'create' + } + + state = event['check']['state'] || 'unknown::2.0_event' + + # Attempt to map 2.0 event state to 1.4 event action + event['action'] ||= action_state_mapping[state.downcase] + # Fallback action is 2.0 event state + event['action'] ||= state + + ## + # Mimic 1.4 event history based on 2.0 event history + # Note: This overwrites the same history attribute + # 2.x history is an array of hashes, each hash includes status + # 1.x history is an array of statuses + ## + if event['check']['history'] + # Let's save the original history + history_v2 = Marshal.load(Marshal.dump(event['check']['history'])) + event['check']['history_v2'] = history_v2 + legacy_history = [] + event['check']['history'].each do |h| + legacy_history << h['status'].to_i.to_s || '3' + end + event['check']['history'] = legacy_history + end + + ## + # Setting flag indicating this function has already been called + ## + event['v2_event_mapped_into_v1'] = true + end + # return the updated event + event + end + def net_http_req_class(method) case method.to_s.upcase when 'GET' diff --git a/test/external/handle-2to1 b/test/external/handle-2to1 new file mode 100755 index 0000000..3461294 --- /dev/null +++ b/test/external/handle-2to1 @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby +require 'sensu-handler' + +class Helpers < Sensu::Handler + def handle + puts event_summary + end + + def api_request(*_args) + nil + end + + def stash_exists?(*_args) + nil + end + + def event_exists?(*_args) + true + end + + def event_summary + client_name = @event['client']['name'] + check_name = @event['check']['name'] + source = @event['check']['source'] + output = @event['check']['output'] + total_state_change = @event['check']['total_state_change'] + action = @event['action'] + client_subscribers = @event['client']['subscribers'].join('|') + check_subscribers = @event['client']['subscribers'].join('^') + history = @event['check']['history'].join('') + [client_name, check_name, source, output, total_state_change, action, client_subscribers, check_subscribers, history].join(' : ') + end +end diff --git a/test/fixtures/basic_v2_event.json b/test/fixtures/basic_v2_event.json new file mode 100644 index 0000000..3699537 --- /dev/null +++ b/test/fixtures/basic_v2_event.json @@ -0,0 +1 @@ +{"entity":{"id":"test_entity","subscriptions":["sub1","sub2","sub3"]},"check":{"name":"test_check","output":"test_output","subscriptions":["sub1","sub2","sub3"],"proxy_entity_id":"test_proxy","total_state_change":4,"state":"failing","history":[{"status":0,"executed":0},{"status":1,"executed":1},{"status":2,"executed":2},{"status":3,"executed":3},{"status":0,"executed":4}],"status":0}} diff --git a/test/handle_2to1_test.rb b/test/handle_2to1_test.rb new file mode 100755 index 0000000..8a4486b --- /dev/null +++ b/test/handle_2to1_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' +require 'English' + +class TestHandle2to1 < MiniTest::Test + include SensuPluginTestHelper + + def setup + set_script 'external/handle-2to1 --map-v2-event-into-v1' + end + + def test_2to1_enabled + event = JSON.parse(fixture('basic_v2_event.json').read) + expected = "test_entity : test_check : test_proxy : test_output : 4 : create : sub1|sub2|sub3 : sub1^sub2^sub3 : 01230\n" + output = run_script_with_input(JSON.generate(event)) + assert_equal(0, $CHILD_STATUS.exitstatus) + assert_match(expected, output) + end +end diff --git a/test/mutator_2to1_test.rb b/test/mutator_2to1_test.rb new file mode 100755 index 0000000..c974f5d --- /dev/null +++ b/test/mutator_2to1_test.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +require 'English' + +require 'test_helper' + +# Simple Heper to test mutator +class TestMutatorHelpers < MiniTest::Test + include SensuPluginTestHelper + def test_base_2to1_mutator + set_script 'external/mutator-trivial --map-v2-event-into-v1' + event = JSON.parse(fixture('basic_v2_event.json').read) + output = run_script_with_input(JSON.generate(event)) + assert_equal(0, $CHILD_STATUS.exitstatus) + assert_equal(event['entity']['id'], JSON.parse(output)['client']['name']) + end + + def test_external_2to1_mutator + set_script 'external/mutator-helpers --map-v2-event-into-v1' + event = JSON.parse(fixture('basic_v2_event.json').read) + output = run_script_with_input(JSON.generate(event)) + assert_equal(0, $CHILD_STATUS.exitstatus) + assert_equal(true, JSON.parse(output)['mutated']) + end +end