Skip to content

Commit

Permalink
(#597) Add sensu::contact type (Enterprise Only)
Browse files Browse the repository at this point in the history
Without this patch the Puppet module doesn't provide an affordance for [contact
routing](https://sensuapp.org/docs/0.29/enterprise/contact-routing.html)  This
patch addresses the problem by adding a new defined type, sensu::contact backed
by a custom type and provider for sensu_contact.

Example usage:

    sensu::contact { 'support':
      config => { 'email'  => { 'to' => 'support@example.com' } },
    }
    sensu::contact { 'ops':
      config => { 'email'  => { 'to' => 'ops@example.com' } },
    }
    sensu::check { 'check_ntp':
      command     => 'PATH=$PATH:/usr/lib64/nagios/plugins check_ntp_time -H pool.ntp.org -w 30 -c 60',
      handlers    => 'email',
      contacts    => ['ops', 'support'],
    }

Resolves #597
  • Loading branch information
jeffmccune committed Jul 11, 2017
1 parent bd97d83 commit 89e7918
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 2 deletions.
48 changes: 48 additions & 0 deletions lib/puppet/provider/sensu_contact/json.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require 'json' if Puppet.features.json?
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..',
'puppet_x', 'sensu', 'provider_create.rb'))

Puppet::Type.type(:sensu_contact).provide(:json) do
confine :feature => :json
include PuppetX::Sensu::ProviderCreate

def conf
begin
@conf ||= JSON.parse(File.read(config_file))
rescue
@conf ||= {}
end
end

def flush
File.open(config_file, 'w') do |f|
f.puts JSON.pretty_generate(conf)
end
end

def pre_create
conf['contacts'] = {}
conf['contacts'][resource[:name]] = {}
self.config = resource[:config]
end

def config_file
"#{resource[:base_path]}/#{resource[:name]}.json"
end

def destroy
@conf = nil
end

def exists?
conf.has_key?('contacts') and conf['contacts'].has_key?(resource[:name])
end

def config
conf['contacts'][resource[:name]]
end

def config=(value)
conf['contacts'][resource[:name]] = value
end
end
11 changes: 11 additions & 0 deletions lib/puppet/type/sensu_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ def insync?(is)
end
end

newproperty(:contacts, :array_matching => :all, :parent => SensuCheckArrayProperty) do
desc "Contact names to override handler configuration via Contact Routing"
# Valid names documented at
# https://sensuapp.org/docs/0.29/enterprise/contact-routing.html#contact-names
newvalues(/^[\w\.-]+$/, :absent)
def insync?(is)
return is.sort == should.sort if is.is_a?(Array) && should.is_a?(Array)
is == should
end
end

newproperty(:high_flap_threshold) do
desc "A host is determined to be flapping when the percent change exceedes this threshold."
newvalues(/.*/, :absent)
Expand Down
61 changes: 61 additions & 0 deletions lib/puppet/type/sensu_contact.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
Puppet::Type.newtype(:sensu_contact) do
@doc = ""

def initialize(*args)
super *args

if c = catalog
self[:notify] = [
'Service[sensu-server]',
'Service[sensu-enterprise]',
].select { |ref| c.resource(ref) }
end
end

ensurable do
newvalue(:present) do
provider.create
end

newvalue(:absent) do
provider.destroy
end

defaultto :present
end

newparam(:name) do
isnamevar
# Valid names documented at
# https://sensuapp.org/docs/0.29/enterprise/contact-routing.html#contact-names
newvalues(/^[\w\.-]+$/)
desc 'The name of the contact, e.g. "support"'
end

newproperty(:config) do
desc 'Configuration hash for the contact.'

def is_to_s(hash = @is)
hash.keys.sort.map {|key| "#{key} => #{hash[key]}"}.join(", ")
end

def should_to_s(hash = @should[0])
hash.keys.sort.map {|key| "#{key} => #{hash[key]}"}.join(", ")
end

def insync?(is)
is_to_s(is) == should_to_s
end

defaultto {}
end

newparam(:base_path) do
desc 'The base path to the contact config file'
defaultto '/etc/sensu/conf.d/contacts/'
end

autorequire(:package) do
['sensu']
end
end
8 changes: 8 additions & 0 deletions manifests/check.pp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
# Array of Strings. Handlers to use for this check
# Set this to 'absent' to remove it completely.
# Default: undef

# [*contacts*]
# Array of Strings. Contacts to use for the contact-routing
# Sensu Enterprise feature. This value corresponds with a sensu::contact
# resource having the same name.
# Default: undef
#
# [*standalone*]
# Boolean. When true, scheduled by the client. When false, listen for published check request
Expand Down Expand Up @@ -109,6 +115,7 @@
$ensure = 'present',
$type = undef,
$handlers = undef,
$contacts = undef,
$standalone = true,
$interval = 60,
$occurrences = undef,
Expand Down Expand Up @@ -215,6 +222,7 @@
standalone => $standalone,
command => $command,
handlers => $handlers,
contacts => $contacts,
interval => $interval,
occurrences => $occurrences,
refresh => $refresh,
Expand Down
55 changes: 55 additions & 0 deletions manifests/contact.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# = Define: sensu::routing
#
# Manage [Contact
# Routing](https://sensuapp.org/docs/0.26/enterprise/contact-routing.html)
# configuration with Sensu Enterprise.
#
# Note: If the `sensu::purge_config` class parameter is `true`, unmanaged
# sensu::contact resources located in /etc/sensu/conf.d/contacts will be purged.
#
# == Parameters
#
# [*ensure*]
# String. Whether the check should be present or not
# Default: present
# Valid values: present, absent
#
# [*base_path*]
# String. Where to place the contact JSON configuration file. Defaults to
# `undef` which defers to the behavior of the underlying sensu_contact type.
# Default: undef
#
# [*config*]
# Hash. The configuration data for the contact. This is an arbitrary hash to
# accommodate the various communication channels. For example, `{ "email": {
# "to": "support@example.com" } }`.
# Default: {}
define sensu::contact(
$ensure = 'present',
$base_path = undef,
$config = {},
) {
validate_re($ensure, ['^present$', '^absent$'] )
validate_hash($config)

$file_ensure = $ensure ? {
'absent' => 'absent',
default => 'file'
}

# handler configuration may contain "secrets"
file { "/etc/sensu/conf.d/contacts/${name}.json":
ensure => $file_ensure,
owner => 'sensu',
group => 'sensu',
mode => '0440',
before => Sensu_contact[$name],
}

sensu_contact { $name:
ensure => $ensure,
config => $config,
base_path => $base_path,
require => File['/etc/sensu/conf.d/contacts'],
}
}
2 changes: 1 addition & 1 deletion manifests/package.pp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
}
}

file { [ $::sensu::conf_dir, "${sensu::conf_dir}/handlers", "${sensu::conf_dir}/checks", "${sensu::conf_dir}/filters", "${sensu::conf_dir}/extensions", "${sensu::conf_dir}/mutators" ]:
file { [ $::sensu::conf_dir, "${sensu::conf_dir}/handlers", "${sensu::conf_dir}/checks", "${sensu::conf_dir}/filters", "${sensu::conf_dir}/extensions", "${sensu::conf_dir}/mutators", "${sensu::conf_dir}/contacts" ]:
ensure => directory,
owner => $::sensu::user,
group => $::sensu::group,
Expand Down
59 changes: 59 additions & 0 deletions spec/defines/sensu_contact_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require 'spec_helper'

describe 'sensu::contact', :type => :define do
let(:facts) { { 'Class[sensu::service::server]' => true } }
let(:params) { { } }

let(:pre_condition) do
<<-'ENDofPUPPETcode'
include ::sensu
ENDofPUPPETcode
end
let(:title) { 'support' }

context 'default (ensure => present)' do
it 'manages the config file ownership and permissions' do
expect(subject).to contain_file('/etc/sensu/conf.d/contacts/support.json').with(
ensure: 'file',
owner: 'sensu',
group: 'sensu',
mode: '0440',
)
end
it 'defaults to an empty config hash' do
expect(subject).to contain_sensu_contact('support').with(ensure: 'present', config: {})
end
end

describe 'ensure => absent' do
let(:params) { { ensure: 'absent' } }
it { is_expected.to contain_sensu_contact(title).with_ensure('absent') }
it do
is_expected.to contain_file("/etc/sensu/conf.d/contacts/#{title}.json").
with_ensure('absent')
end
end

describe 'config param' do
let(:params) { { config: { 'email' => { 'to' => 'support@example.com' } } } }

it 'passes the config hash to sensu_contact' do
is_expected.to contain_sensu_contact(title).with_config(params[:config])
end
end

describe 'base_path param' do
context 'when specified' do
let(:params) { { base_path: '/tmp/foo' } }

it 'passes the base_path string to sensu_contact' do
is_expected.to contain_sensu_contact(title).with_base_path('/tmp/foo')
end
end
context 'when not specified' do
it 'defers to sensu_contact by passing undef' do
is_expected.to contain_sensu_contact(title).without_base_path
end
end
end
end
28 changes: 27 additions & 1 deletion spec/unit/sensu_check_spec.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
require 'spec_helper'

describe Puppet::Type.type(:sensu_check) do
let(:resource_hash) do
let(:resource_hash_base) do
{
:title => 'foo.example.com',
:catalog => Puppet::Resource::Catalog.new
}
end
# Overridden on a context by context basis
let(:resource_hash_override) { {} }
let(:resource_hash) { resource_hash_base.merge(resource_hash_override) }

describe 'contacts parameter' do
subject { described_class.new(resource_hash)[:contacts] }

valid = [%w(support), %w(support ops), 'support']
invalid = [%w(supp%ort), %w(support op$), 'sup%port']

valid.each do |val|
describe "valid: contacts => #{val.inspect} " do
let(:resource_hash_override) { {contacts: val} }
it { is_expected.to eq [*val] }
end
end

invalid.each do |val|
describe "invalid: contacts => #{val.inspect}" do
let(:resource_hash_override) { {contacts: val} }
it do
expect { subject }.to raise_error Puppet::ResourceError, /Parameter contacts failed/
end
end
end
end

describe 'handlers' do
it 'should support a string as a value' do
Expand Down
48 changes: 48 additions & 0 deletions spec/unit/sensu_contact_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
require 'spec_helper'

describe Puppet::Type.type(:sensu_contact) do
let(:resource_hash_base) do
{
:title => 'support',
:catalog => Puppet::Resource::Catalog.new
}
end
# This is overridden on a context by context basis
let(:resource_hash_override) { {} }
let(:resource_hash) { resource_hash_base.merge(resource_hash_override) }

describe 'name parameter' do
subject { described_class.new(resource_hash)[:name] }
describe 'valid name "support"' do
it { is_expected.to eq 'support' }
end
describe 'invalid name "invalid%name"' do
let(:resource_hash_override) { {name: 'invalid%name'} }
it do
expect { subject }.to raise_error Puppet::ResourceError, /Parameter name failed/
end
end
end

describe 'notifications' do
context 'when managing sensu-enterprise (#495)' do
let(:service_resource) do
Puppet::Type.type(:service).new(name: 'sensu-enterprise')
end
let(:resource_hash) do
c = Puppet::Resource::Catalog.new
c.add_resource(service_resource)
{
:title => 'mymutator',
:catalog => c
}
end

it 'notifies Service[sensu-enterprise]' do
notify_list = described_class.new(resource_hash)[:notify]
# compare the resource reference strings, the object identities differ.
expect(notify_list.map(&:ref)).to eq [service_resource.ref]
end
end
end
end
16 changes: 16 additions & 0 deletions tests/provision_enterprise_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ if [ -z "${FACTER_SE_USER:-}" ]; then
fi
echo "FACTER_SE_USER=$FACTER_SE_USER"

# Save the enterprise username and password, make them work with sudo so that
# the following works:
# vagrant ssh sensu-server-enterprise
# sudo puppet apply /vagrant/test/sensu-server-enterprise.pp

cat > ~/.bash_profile <<'EOF'
[ -f ~/.bashrc ] && source ~/.bashrc
export PATH=$PATH:$HOME/.local/bin:$HOME/bin
# Pass these two key facts through sudo invocations to puppet
alias sudo='sudo FACTER_SE_USER=$FACTER_SE_USER FACTER_SE_PASS=$FACTER_SE_PASS'
EOF
cat >> ~/.bash_profile <<EOF
export FACTER_SE_USER='${FACTER_SE_USER}'
export FACTER_SE_PASS='${FACTER_SE_PASS}'
EOF

# setup module dependencies
puppet module install puppetlabs/rabbitmq

Expand Down
Loading

0 comments on commit 89e7918

Please sign in to comment.