Browse files

Notifier::HttpPost

Adds a generic HTTP POST notifier.

closes #371, closes #406
  • Loading branch information...
1 parent a063da6 commit a07d19dca42b4eadda9c3cf32777691837ab6767 Brian D. Burns committed May 25, 2013
Showing with 471 additions and 2 deletions.
  1. +1 −0 README.md
  2. +1 −0 lib/backup.rb
  3. +2 −1 lib/backup/config.rb
  4. +123 −0 lib/backup/notifier/http_post.rb
  5. +1 −1 spec/cli_spec.rb
  6. +308 −0 spec/notifier/http_post_spec.rb
  7. +35 −0 templates/cli/notifier/http_post
View
1 README.md
@@ -125,6 +125,7 @@ Supported notification services include:
- Prowl
- Hipchat
- Pushover
+- POST Request
## Generators
View
1 lib/backup.rb
@@ -99,6 +99,7 @@ module Notifier
autoload :Prowl, File.join(NOTIFIER_PATH, 'prowl')
autoload :Hipchat, File.join(NOTIFIER_PATH, 'hipchat')
autoload :Pushover, File.join(NOTIFIER_PATH, 'pushover')
+ autoload :HttpPost, File.join(NOTIFIER_PATH, 'http_post')
end
##
View
3 lib/backup/config.rb
@@ -115,7 +115,8 @@ def add_dsl_constants!
{ 'RSync' => ['Push', 'Pull', 'Local'] }
],
# Notifiers
- ['Mail', 'Twitter', 'Campfire', 'Prowl', 'Hipchat', 'Pushover']
+ ['Mail', 'Twitter', 'Campfire', 'Prowl',
+ 'Hipchat', 'Pushover', 'HttpPost']
]
)
end
View
123 lib/backup/notifier/http_post.rb
@@ -0,0 +1,123 @@
+# encoding: utf-8
+require 'excon'
+require 'uri'
+
+module Backup
+ module Notifier
+ class HttpPost < Base
+
+ ##
+ # URI to post notification to.
+ #
+ # URI scheme may be `http` or `https`.
+ #
+ # If Basic Authentication is needed, supply the `user:password` in the URI.
+ # e.g. 'https://user:pass@www.example.com/path'
+ #
+ # Port may also be supplied.
+ # e.g. 'http://www.example.com:8080/path'
+ attr_accessor :uri
+
+ ##
+ # Hash of additional HTTP headers to send.
+ #
+ # This notifier sets the following headers:
+ # { 'User-Agent' => "Backup/#{ Backup::VERSION }",
+ # 'Content-Type' => 'x-www-form-urlencoded' }
+ #
+ # 'Content-Type' may not be changed.
+ # 'User-Agent' may be overridden or omitted by setting it to +nil+.
+ # e.g. { 'Authorization' => 'my_auth_info', 'User-Agent' => nil }
+ attr_accessor :headers
+
+ ##
+ # Hash of additional POST parameters to send.
+ #
+ # This notifier will set two parameters:
+ # { 'status' => 'success|warning|failure',
+ # 'message' => '[Backup::(Success|Warning|Failure)] label (trigger)' }
+ #
+ # 'status' may not be changed.
+ # 'message' may be overridden or omitted by setting a +nil+ value.
+ # e.g. { 'auth_token' => 'my_token', 'message' => nil }
+ attr_accessor :params
+
+ ##
+ # Successful HTTP Status Code(s) that should be returned.
+ #
+ # This may be a single code or an Array of acceptable codes.
+ # e.g. [200, 201, 204]
+ #
+ # If any other response code is returned, the request will be retried
+ # using `max_retries` and `retry_waitsec`.
+ #
+ # Default: 200
+ attr_accessor :success_codes
+
+ ##
+ # Verify the server's certificate when using SSL.
+ #
+ # This will default to +true+ for most systems.
+ # It may be forced by setting to +true+, or disabled by setting to +false+.
+ attr_accessor :ssl_verify_peer
+
+ ##
+ # Path to a +cacert.pem+ file to use for +ssl_verify_peer+.
+ #
+ # This is provided (via Excon), but may be specified if needed.
+ attr_accessor :ssl_ca_file
+
+ def initialize(model, &block)
+ super
+ instance_eval(&block) if block_given?
+
+ @headers ||= {}
+ @params ||= {}
+ @success_codes ||= 200
+ end
+
+ private
+
+ ##
+ # Notify the user of the backup operation results.
+ #
+ # `status` indicates one of the following:
+ #
+ # `:success`
+ # : The backup completed successfully.
+ # : Notification will be sent if `on_success` is `true`.
+ #
+ # `:warning`
+ # : The backup completed successfully, but warnings were logged.
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
+ #
+ # `:failure`
+ # : The backup operation failed.
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
+ #
+ def notify!(status)
+ tag = case status
+ when :success then '[Backup::Success]'
+ when :failure then '[Backup::Failure]'
+ when :warning then '[Backup::Warning]'
+ end
+ message = "#{ tag } #{ model.label } (#{ model.trigger })"
+
+ opts = {
+ :headers => { 'User-Agent' => "Backup/#{ VERSION }" }.
+ merge(headers).reject {|k,v| v.nil? }.
+ merge('Content-Type' => 'application/x-www-form-urlencoded'),
+ :body => encode_www_form({ 'message' => message }.
+ merge(params).reject {|k,v| v.nil? }.
+ merge('status' => status.to_s)),
+ :expects => success_codes # raise error if unsuccessful
+ }
+ opts.merge!(:ssl_verify_peer => ssl_verify_peer) unless ssl_verify_peer.nil?
+ opts.merge!(:ssl_ca_file => ssl_ca_file) if ssl_ca_file
+
+ Excon.post(uri, opts)
+ end
+
+ end
+ end
+end
View
2 spec/cli_spec.rb
@@ -584,7 +584,7 @@
syncers (cloud_files, rsync_local, rsync_pull, rsync_push, s3)
encryptors (gpg, openssl)
compressors (bzip2, custom, gzip, lzma, pbzip2)
- notifiers (campfire, hipchat, mail, prowl, pushover, twitter)
+ notifiers (campfire, hipchat, http_post, mail, prowl, pushover, twitter)
EOS
out, err = capture_io do
View
308 spec/notifier/http_post_spec.rb
@@ -0,0 +1,308 @@
+# encoding: utf-8
+
+require File.expand_path('../../spec_helper.rb', __FILE__)
+
+module Backup
+describe Notifier::HttpPost do
+ let(:model) { Model.new(:test_trigger, 'test label') }
+ let(:notifier) {
+ Notifier::HttpPost.new(model) do |post|
+ post.uri = 'https://www.example.com/path'
+ end
+ }
+ let(:default_form_data) {
+ 'message=%5BBackup%3A%3ASuccess%5D+test+label+%28test_trigger%29' +
+ '&status=success'
+ }
+ let(:default_headers) {
+ { 'User-Agent' => "Backup/#{ VERSION }",
+ 'Content-Type' => 'application/x-www-form-urlencoded' }
+ }
+
+ it_behaves_like 'a class that includes Configuration::Helpers'
+ it_behaves_like 'a subclass of Notifier::Base'
+
+ describe '#initialize' do
+ it 'provides default values' do
+ notifier = Notifier::HttpPost.new(model)
+
+ expect( notifier.uri ).to be_nil
+ expect( notifier.headers ).to eq({})
+ expect( notifier.params ).to eq({})
+ expect( notifier.success_codes ).to be 200
+ expect( notifier.ssl_verify_peer ).to be_nil
+ expect( notifier.ssl_ca_file ).to be_nil
+
+ expect( notifier.on_success ).to be(true)
+ expect( notifier.on_warning ).to be(true)
+ expect( notifier.on_failure ).to be(true)
+ expect( notifier.max_retries ).to be(10)
+ expect( notifier.retry_waitsec ).to be(30)
+ end
+
+ it 'configures the notifier' do
+ notifier = Notifier::HttpPost.new(model) do |post|
+ post.uri = 'my_uri'
+ post.headers = 'my_headers'
+ post.params = 'my_params'
+ post.success_codes = 'my_success_codes'
+ post.ssl_verify_peer = 'my_ssl_verify_peer'
+ post.ssl_ca_file = 'my_ssl_ca_file'
+
+ post.on_success = false
+ post.on_warning = false
+ post.on_failure = false
+ post.max_retries = 5
+ post.retry_waitsec = 10
+ end
+
+ expect( notifier.uri ).to eq 'my_uri'
+ expect( notifier.headers ).to eq 'my_headers'
+ expect( notifier.params ).to eq 'my_params'
+ expect( notifier.success_codes ).to eq 'my_success_codes'
+ expect( notifier.ssl_verify_peer ).to eq 'my_ssl_verify_peer'
+ expect( notifier.ssl_ca_file ).to eq 'my_ssl_ca_file'
+
+ expect( notifier.on_success ).to be(false)
+ expect( notifier.on_warning ).to be(false)
+ expect( notifier.on_failure ).to be(false)
+ expect( notifier.max_retries ).to be(5)
+ expect( notifier.retry_waitsec ).to be(10)
+ end
+ end # describe '#initialize'
+
+ describe '#headers' do
+
+ it 'defines additional headers to be sent' do
+ notifier.headers = { 'Authorization' => 'my_auth' }
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => { 'User-Agent' => "Backup/#{ VERSION }",
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'Authorization' => 'my_auth' },
+ :body => default_form_data,
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ it 'may overrided the User-Agent header' do
+ notifier.headers = { 'Authorization' => 'my_auth', 'User-Agent' => 'my_app' }
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => { 'User-Agent' => 'my_app',
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'Authorization' => 'my_auth' },
+ :body => default_form_data,
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ it 'may omit the User-Agent header' do
+ notifier.headers = { 'Authorization' => 'my_auth', 'User-Agent' => nil }
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => { 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'Authorization' => 'my_auth' },
+ :body => default_form_data,
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ end # describe '#headers'
+
+ describe '#params' do
+
+ it 'defines additional form parameters to be sent' do
+ notifier.params = { 'my_param' => 'my_value' }
+ form_data = 'message=%5BBackup%3A%3ASuccess%5D+test+label+%28test_trigger%29' +
+ '&my_param=my_value&status=success'
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => form_data,
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ it 'may override the `message` parameter' do
+ notifier.params = { 'my_param' => 'my_value', 'message' => 'my message' }
+ form_data = 'message=my+message&my_param=my_value&status=success'
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => form_data,
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ it 'may omit the `message` parameter' do
+ notifier.params = { 'my_param' => 'my_value', 'message' => nil }
+ form_data = 'my_param=my_value&status=success'
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => form_data,
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ end # describe '#params'
+
+ describe '#success_codes' do
+
+ it 'specifies expected http success codes' do
+ notifier.success_codes = [200, 201]
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => default_form_data,
+ :expects => [200, 201]
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+ end # describe '#success_codes'
+
+ describe '#ssl_verify_peer' do
+
+ it 'may force enable verification' do
+ notifier.ssl_verify_peer = true
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => default_form_data,
+ :expects => 200,
+ :ssl_verify_peer => true
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ it 'may disable verification' do
+ notifier.ssl_verify_peer = false
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => default_form_data,
+ :expects => 200,
+ :ssl_verify_peer => false
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+
+ end # describe '#ssl_verify_peer'
+
+ describe '#ssl_ca_file' do
+
+ it 'specifies path to a custom cacert.pem file' do
+ notifier.ssl_ca_file = '/my/cacert.pem'
+
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => default_form_data,
+ :expects => 200,
+ :ssl_ca_file => '/my/cacert.pem'
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+ end # describe '#ssl_ca_file'
+
+ describe '#notify!' do
+ let(:form_data) {
+ 'message=%5BBackup%3A%3A' + 'TAG' +
+ '%5D+test+label+%28test_trigger%29&status=' + 'STATUS'
+ }
+
+ context 'when status is :success' do
+ it 'sends a success message' do
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => form_data.sub('TAG', 'Success').sub('STATUS', 'success'),
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :success)
+ end
+ end
+
+ context 'when status is :warning' do
+ it 'sends a warning message' do
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => form_data.sub('TAG', 'Warning').sub('STATUS', 'warning'),
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :warning)
+ end
+ end
+
+ context 'when status is :failure' do
+ it 'sends a failure message' do
+ Excon.expects(:post).with(
+ 'https://www.example.com/path',
+ {
+ :headers => default_headers,
+ :body => form_data.sub('TAG', 'Failure').sub('STATUS', 'failure'),
+ :expects => 200
+ }
+ )
+
+ notifier.send(:notify!, :failure)
+ end
+ end
+
+ end # describe '#notify!'
+
+end
+end
View
35 templates/cli/notifier/http_post
@@ -0,0 +1,35 @@
+ ##
+ # HttpPost [Notifier]
+ #
+ # For details, see:
+ # https://github.com/meskyanichi/backup/wiki/Notifiers
+ #
+ notify_by HttpPost do |post|
+ post.on_success = true
+ post.on_warning = true
+ post.on_failure = true
+
+ # URI to post the notification to.
+ # Port may be specified if needed.
+ # If Basic Authentication is required, supply user:pass.
+ post.uri = 'https://user:pass@your.domain.com:8443/path'
+
+ ##
+ # Optional
+ #
+ # Additional headers to send.
+ # post.headers = { 'Authentication' => 'my_auth_info' }
+ #
+ # Additional form params to post.
+ # post.params = { 'auth_token' => 'my_token' }
+ #
+ # Successful response codes. Default: 200
+ # post.success_codes = [200, 201, 204]
+ #
+ # Defaults to true on most systems.
+ # Force with +true+, disable with +false+
+ # post.ssl_verify_peer = false
+ #
+ # Supplied by default. Override with a custom 'cacert.pem' file.
+ # post.ssl_ca_file = '/my/cacert.pem'
+ end

0 comments on commit a07d19d

Please sign in to comment.