├── sensu
├── files
│ ├── extensions
│ │ └── .gitignore
│ ├── handlers
│ │ └── .gitignore
│ ├── mutators
│ │ └── .gitignore
│ ├── yum
│ │ └── osmajorrelease.template
│ ├── conf.d
│ │ └── check_cron.json
│ ├── windows
│ │ └── sensu-client.xml
│ └── plugins
│ │ └── check-procs.rb
├── service_map.jinja
├── configfile_map.jinja
├── transport_conf.sls
├── api.sls
├── api_conf.sls
├── redis_conf.sls
├── repos_map.jinja
├── uchiwa.sls
├── rabbitmq_conf.sls
├── init.sls
├── pillar_map.jinja
├── server.sls
└── client.sls
├── .gitattributes
├── CHANGELOG.rst
├── LICENSE
├── pillar.example
└── README.md
/sensu/files/extensions/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sensu/files/handlers/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sensu/files/mutators/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sls linguist-language=SaltStack
2 |
--------------------------------------------------------------------------------
/sensu/files/yum/osmajorrelease.template:
--------------------------------------------------------------------------------
1 | {{ grains['osmajorrelease'] }}
2 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | sensu formula
2 | ================
3 |
4 | 0.0.1 (2014-10-30)
5 |
6 | - Initial version
7 |
--------------------------------------------------------------------------------
/sensu/files/conf.d/check_cron.json:
--------------------------------------------------------------------------------
1 | {% from "sensu/service_map.jinja" import services with context -%}
2 | {
3 | "checks": {
4 | "cron_check": {
5 | "handlers": ["default"],
6 | "command": "/etc/sensu/plugins/check-procs.rb -p {{ services.cron }} -C 1 ",
7 | "interval": 60,
8 | "subscribers": [ "all" ]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/sensu/service_map.jinja:
--------------------------------------------------------------------------------
1 | {% set services = salt['grains.filter_by']({
2 | 'Debian': {
3 | 'cron': 'cron',
4 | 'apache': 'apache2',
5 | 'nagios_plugins': 'nagios-plugins',
6 | },
7 | 'RedHat': {
8 | 'cron': 'crond',
9 | 'apache': 'httpd',
10 | 'nagios_plugins': 'nagios-plugins-all',
11 | },
12 | }, merge=salt['pillar.get']('sensu:lookup'), default='Debian') %}
13 |
14 |
--------------------------------------------------------------------------------
/sensu/configfile_map.jinja:
--------------------------------------------------------------------------------
1 | {% set files = salt['grains.filter_by']({
2 | 'default': {
3 | 'files': {
4 | 'user': 'root',
5 | 'group': 'root',
6 | },
7 | },
8 | 'Windows': {
9 | 'files': {
10 | 'user': 'Administrator',
11 | 'group': 'nowindowsgroup',
12 | },
13 | },
14 | }, merge=salt['pillar.get']('sensu'), default='default') %}
15 |
16 |
--------------------------------------------------------------------------------
/sensu/transport_conf.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context -%}
2 |
3 | include:
4 | - sensu
5 |
6 | /etc/sensu/conf.d/transport.json:
7 | file.serialize:
8 | - formatter: json
9 | - user: root
10 | - group: root
11 | - mode: 644
12 | - require:
13 | - pkg: sensu
14 | - dataset:
15 | transport:
16 | name: {{ sensu.transport.name }}
17 | reconnect_on_error: {{ sensu.transport.reconnect_on_error }}
18 |
--------------------------------------------------------------------------------
/sensu/files/windows/sensu-client.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | sensu-client
6 | Sensu Client
7 | This service runs a Sensu client
8 | C:\opt\sensu\embedded\bin\ruby
9 | C:\opt\sensu\embedded\bin\sensu-client -c C:\etc\sensu\config.json -d C:\etc\sensu\conf.d -l C:\opt\sensu\sensu-client.log
10 |
11 |
--------------------------------------------------------------------------------
/sensu/api.sls:
--------------------------------------------------------------------------------
1 | include:
2 | - sensu
3 | - sensu.api_conf
4 | - sensu.rabbitmq_conf
5 | - sensu.redis_conf
6 |
7 | sensu-api:
8 | service.running:
9 | - enable: True
10 | - require:
11 | - file: /etc/sensu/conf.d/api.json
12 | - file: /etc/sensu/conf.d/rabbitmq.json
13 | - file: /etc/sensu/conf.d/redis.json
14 | - watch:
15 | - file: /etc/sensu/conf.d/api.json
16 | - file: /etc/sensu/conf.d/rabbitmq.json
17 | - file: /etc/sensu/conf.d/redis.json
18 |
--------------------------------------------------------------------------------
/sensu/api_conf.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context -%}
2 |
3 | include:
4 | - sensu
5 |
6 | /etc/sensu/conf.d/api.json:
7 | file.serialize:
8 | - formatter: json
9 | - user: root
10 | - group: root
11 | - mode: 644
12 | - require:
13 | - pkg: sensu
14 | - dataset:
15 | api:
16 | host: {{ sensu.api.host }}
17 | password: {{ sensu.api.password }}
18 | port: {{ sensu.api.port }}
19 | user: {{ sensu.api.user }}
20 |
21 |
--------------------------------------------------------------------------------
/sensu/redis_conf.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context -%}
2 |
3 | /etc/sensu/conf.d/redis.json:
4 | file.serialize:
5 | - formatter: json
6 | - user: root
7 | - group: root
8 | - mode: 644
9 | - require:
10 | - pkg: sensu
11 | - dataset:
12 | redis:
13 | host: {{ sensu.redis.host }}
14 | {% if sensu.redis.password is defined and sensu.redis.password is not none %}password: {{ sensu.redis.password }}{% endif %}
15 | port: {{ sensu.redis.port }}
16 |
17 |
--------------------------------------------------------------------------------
/sensu/repos_map.jinja:
--------------------------------------------------------------------------------
1 | {% set repos = salt['grains.filter_by']({
2 | 'Debian': {
3 | 'enabled': True,
4 | 'name': 'deb https://sensu.global.ssl.fastly.net/apt ' ~ grains.oscodename ~ ' main',
5 | 'key_url': 'https://sensu.global.ssl.fastly.net/apt/pubkey.gpg',
6 | },
7 | 'RedHat': {
8 | 'enabled': True,
9 | 'baseurl': 'https://sensu.global.ssl.fastly.net/yum/$releasever/$basearch/',
10 | 'gpgcheck': '0',
11 | },
12 | 'Windows': {
13 | 'enabled': False,
14 | }
15 | }, merge=salt['pillar.get']('sensu:lookup:repos'), default='Debian') %}
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Salt Stack Formulas
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/sensu/uchiwa.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context -%}
2 |
3 | include:
4 | - sensu
5 |
6 | uchiwa:
7 | pkg.installed:
8 | - require:
9 | - pkgrepo: sensu
10 | file.serialize:
11 | - name: /etc/sensu/uchiwa.json
12 | - formatter: json
13 | - mode: 644
14 | - user: uchiwa
15 | - group: sensu
16 | - require:
17 | - pkg: uchiwa
18 | - dataset:
19 | sensu:
20 | {{ sensu.sites }}
21 | uchiwa:
22 | {{ sensu.uchiwa }}
23 |
24 | service.running:
25 | - enable: True
26 | - require:
27 | - file: /etc/sensu/uchiwa.json
28 | - watch:
29 | - file: /etc/sensu/uchiwa.json
30 |
--------------------------------------------------------------------------------
/sensu/rabbitmq_conf.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context %}
2 | {% from "sensu/configfile_map.jinja" import files with context %}
3 |
4 | include:
5 | - sensu
6 |
7 | /etc/sensu/conf.d/rabbitmq.json:
8 | file.serialize:
9 | - formatter: json
10 | - user: {{files.files.user}}
11 | - group: {{files.files.group}}
12 | - makedirs: True
13 | {% if grains['os_family'] != 'Windows' %}
14 | - mode: 644
15 | {% endif %}
16 | - dataset:
17 | rabbitmq:
18 | host: {{ sensu.rabbitmq.host }}
19 | port: {{ sensu.rabbitmq.port }}
20 | vhost: {{ sensu.rabbitmq.vhost }}
21 | user: {{ sensu.rabbitmq.user }}
22 | password: {{ sensu.rabbitmq.password }}
23 | {% if sensu.ssl.enable %}
24 | ssl:
25 | cert_chain_file: /etc/sensu/ssl/cert.pem
26 | private_key_file: /etc/sensu/ssl/key.pem
27 | {%- endif %}
28 |
29 | {%- if salt['pillar.get']('sensu:ssl:enable', false) %}
30 | /etc/sensu/ssl:
31 | file.directory:
32 | - user: sensu
33 | - group: sensu
34 | - require:
35 | - pkg: sensu
36 |
37 | /etc/sensu/ssl/key.pem:
38 | file.managed:
39 | - contents_pillar: sensu:ssl:key_pem
40 | - require:
41 | - file: /etc/sensu/ssl
42 |
43 | /etc/sensu/ssl/cert.pem:
44 | file.managed:
45 | - contents_pillar: sensu:ssl:cert_pem
46 | - require:
47 | - file: /etc/sensu/ssl
48 | {%- endif %}
49 |
--------------------------------------------------------------------------------
/sensu/init.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/repos_map.jinja" import repos with context -%}
2 |
3 | {% if grains['os_family'] == 'Debian' %}
4 | sensu-extra-packages:
5 | pkg.installed:
6 | - names:
7 | - python-apt
8 | {% if repos.get('enabled') and repos.get('name').startswith("https") %}
9 | - apt-transport-https
10 | {% endif %}
11 | - require_in:
12 | - pkgrepo: sensu
13 | {% endif %}
14 |
15 | {% if grains['os_family'] == 'RedHat' %}
16 | /etc/yum/vars/osmajorrelease:
17 | file.managed:
18 | - source: salt://sensu/files/yum/osmajorrelease.template
19 | - template: jinja
20 | {% endif %}
21 |
22 | sensu:
23 | {% if repos.get('enabled') %}
24 | pkgrepo.managed:
25 | - humanname: Sensu Repository
26 | {% if grains['os_family'] == 'Debian' %}
27 | - name: {{ repos.get('name') }}
28 | - file: /etc/apt/sources.list.d/sensu.list
29 | {%- if repos.get('key_url') %}
30 | - key_url: {{ repos.get('key_url') }}
31 | {%- endif %}
32 | {%- elif grains['os_family'] == 'RedHat' %}
33 | - baseurl: {{ repos.get('baseurl') }}
34 | - gpgcheck: {{ repos.get('gpgcheck') }}
35 | - enabled: 1
36 | {% endif %}
37 | - require_in:
38 | - pkg: sensu
39 | {% endif %}
40 | pkg:
41 | - installed
42 |
43 | {% if grains['os_family'] != 'Windows' %}
44 | old sensu repository:
45 | pkgrepo.absent:
46 | {% if grains['os_family'] == 'Debian' %}
47 | - name: deb http://repos.sensuapp.org/apt sensu main
48 | - keyid: 18609E3D7580C77F # key from http://repos.sensuapp.org/apt/pubkey.gpg
49 | {% elif grains['os_family'] == 'RedHat' %}
50 | - name: http://repos.sensuapp.org/yum/el/$releasever/$basearch/
51 | {% endif %}
52 | - require_in:
53 | - pkg: sensu
54 | {% endif %}
55 |
--------------------------------------------------------------------------------
/pillar.example:
--------------------------------------------------------------------------------
1 | sensu:
2 | lookup:
3 | repos:
4 | name: deb http://repositories.sensuapp.org/apt sensu main
5 | key_url: http://repositories.sensuapp.org/apt/pubkey.gpg
6 | server:
7 | install_gems:
8 | - mail
9 | - timeout
10 | - name: aws-sdk
11 | version: 2.2.6
12 | client:
13 | embedded_ruby: true
14 | nagios_plugins: true
15 | redact:
16 | - password
17 | gem_proxy: http://192.168.1.1:3128/
18 | rabbitmq:
19 | host: 10.0.0.1
20 | user: sensu
21 | password: secret
22 | api:
23 | password: secret
24 | ssl:
25 | enable: True
26 | cert_pem: |
27 | < PUT CERTIFICATE HERE IF USING SLL >
28 | key_pem: |
29 | < PUT PRIVATE KEY HERE IF USING SLL >
30 | uchiwa:
31 | host: localhost
32 | loglevel: warn
33 | users:
34 | - username: bobby
35 | password: testy
36 | role:
37 | readonly: True
38 | sites:
39 | - name: 'Site 1'
40 | host: '1.1.1.1'
41 | user: bobby
42 | pass: secret
43 | - name: 'Site 2'
44 | host: 1.2.3.4
45 | user: nicky
46 | pass: secret
47 | ssl: True
48 | # Configurable path support.
49 | paths:
50 | plugins: monitoring/checks
51 | handlers: monitoring/handlers
52 | # Optional: Checks within Pillar
53 | checks:
54 | check_http:
55 | handlers:
56 | - pagerduty
57 | command: "/etc/sensu/plugins/check-http.rb -h localhost -p 443 -s -e 30"
58 | interval: 300
59 | occurences: 2
60 | subscribers:
61 | - ssl_server
62 | # Optional: Pillarized Handler files
63 | handlers:
64 | handlers:
65 | pagerduty:
66 | type: pipe
67 | command: /etc/sensu/handlers/pagerduty.rb
68 | pagerduty:
69 | api_key: "encrypted or secure apikey"
70 |
--------------------------------------------------------------------------------
/sensu/pillar_map.jinja:
--------------------------------------------------------------------------------
1 | {% set sensu = salt['grains.filter_by']({
2 | 'default': {
3 | 'transport': {
4 | 'name': 'rabbitmq',
5 | 'reconnect_on_error': 'true',
6 | },
7 | 'client': {
8 | 'embedded_ruby': False,
9 | 'nagios_plugins': False,
10 | 'name': salt['grains.get']('fqdn'),
11 | 'address': salt['grains.get']('ipv4')[0],
12 | 'subscriptions': ['all'],
13 | 'safe_mode': False
14 | },
15 | 'redis': {
16 | 'host': 'localhost',
17 | 'port': 6379,
18 | 'password': None,
19 | },
20 | 'api': {
21 | 'host': 'localhost',
22 | 'port': 4567,
23 | 'user': 'admin',
24 | 'password': ''
25 | },
26 | 'rabbitmq': {
27 | 'host': 'localhost',
28 | 'port': 5672,
29 | 'vhost': '/sensu',
30 | 'user': 'sensu',
31 | 'password': ''
32 | },
33 | 'server': {
34 | 'embedded_ruby': False,
35 | },
36 | 'ssl': {
37 | 'enable': False
38 | },
39 | 'uchiwa': {
40 | 'host': '0.0.0.0',
41 | 'port': 3000,
42 | 'loglevel': 'info',
43 | 'refresh': 10,
44 | 'stats': 10,
45 | 'users': [
46 | {
47 | 'username': 'admin',
48 | 'password': 'secret',
49 | 'role': { 'readonly': False }
50 | },
51 | {
52 | 'username': 'guest',
53 | 'password': 'secret',
54 | 'role': { 'readonly': True }
55 | }
56 | ]
57 | },
58 | 'sites': [
59 | {
60 | 'name': 'Sensu',
61 | 'host': '127.0.0.1',
62 | 'port': 4567,
63 | 'ssl': False,
64 | 'insecure': False,
65 | 'path': '',
66 | 'user': 'admin',
67 | 'password': '',
68 | 'timeout': 5
69 | }
70 | ],
71 | 'paths': {
72 | 'plugins' : 'sensu/files/plugins',
73 | 'conf_d' : 'sensu/files/conf.d',
74 | 'extensions' : 'sensu/files/extensions',
75 | 'mutators' : 'sensu/files/mutators',
76 | 'handlers' : 'sensu/files/handlers',
77 | 'checks_file' : '/etc/sensu/conf.d/checks.json',
78 | 'standalone_checks_file' : '/etc/sensu/conf.d/standalone_checks.json',
79 | 'handlers_file' : '/etc/sensu/conf.d/handlers.json',
80 | },
81 | },
82 | }, merge=salt['pillar.get']('sensu'), default='default') %}
83 |
--------------------------------------------------------------------------------
/sensu/server.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context -%}
2 |
3 | include:
4 | - sensu
5 | - sensu.api_conf # Some handlers need to access the API server
6 | - sensu.rabbitmq_conf
7 | - sensu.redis_conf
8 |
9 | /etc/sensu/conf.d:
10 | file.recurse:
11 | - source: salt://{{ sensu.paths.conf_d }}
12 | - template: jinja
13 | - require:
14 | - pkg: sensu
15 | - watch_in:
16 | - service: sensu-server
17 |
18 | {%- if salt['pillar.get']('sensu:checks') %}
19 | sensu_subscription_checks_file:
20 | file.serialize:
21 | - name: {{ sensu.paths.checks_file }}
22 | - dataset:
23 | checks: {{ salt['pillar.get']('sensu:checks') }}
24 | - formatter: json
25 | - require:
26 | - pkg: sensu
27 | - watch_in:
28 | - service: sensu-server
29 | {%- else %}
30 | sensu_subscription_checks_file:
31 | file.absent:
32 | - name: {{ sensu.paths.checks_file }}
33 | {%- endif %}
34 |
35 | {%- if salt['pillar.get']('sensu:handlers') %}
36 |
37 | sensu_handlers_file:
38 | file.serialize:
39 | - name: {{ sensu.paths.handlers_file }}
40 | - dataset_pillar: sensu:handlers
41 | - formatter: json
42 | - require:
43 | - pkg: sensu
44 | - watch_in:
45 | - service: sensu-server
46 |
47 | {% endif %}
48 |
49 | /etc/sensu/extensions:
50 | file.recurse:
51 | - source: salt://{{ sensu.paths.extensions }}
52 | - file_mode: 555
53 | - require:
54 | - pkg: sensu
55 | - watch_in:
56 | - service: sensu-server
57 |
58 | /etc/sensu/mutators:
59 | file.recurse:
60 | - source: salt://{{ sensu.paths.mutators }}
61 | - file_mode: 555
62 | - require:
63 | - pkg: sensu
64 | - watch_in:
65 | - service: sensu-server
66 |
67 | /etc/sensu/handlers:
68 | file.recurse:
69 | - source: salt://{{ sensu.paths.handlers }}
70 | - file_mode: 555
71 | - require:
72 | - pkg: sensu
73 | - require_in:
74 | - service: sensu-server
75 | - watch_in:
76 | - service: sensu-server
77 |
78 | {% set gem_list = salt['pillar.get']('sensu:server:install_gems', []) %}
79 | {% for gem in gem_list %}
80 | {% if gem is mapping %}
81 | {% set gem_name = gem.name %}
82 | {% else %}
83 | {% set gem_name = gem %}
84 | {% endif %}
85 | install_{{ gem_name }}:
86 | gem.installed:
87 | - name: {{ gem_name }}
88 | {% if sensu.server.embedded_ruby %}
89 | - gem_bin: /opt/sensu/embedded/bin/gem
90 | {% endif %}
91 | {% if gem.version is defined %}
92 | - version: {{ gem.version }}
93 | {% endif %}
94 | - rdoc: False
95 | - ri: False
96 | - proxy: {{ salt['pillar.get']('sensu:server:gem_proxy') }}
97 | - source: {{ salt['pillar.get']('sensu:server:gem_source') }}
98 | {% endfor %}
99 |
100 | sensu-server:
101 | service.running:
102 | - enable: True
103 | - require:
104 | - file: /etc/sensu/conf.d/redis.json
105 | - file: /etc/sensu/conf.d/rabbitmq.json
106 |
--------------------------------------------------------------------------------
/sensu/client.sls:
--------------------------------------------------------------------------------
1 | {% from "sensu/pillar_map.jinja" import sensu with context %}
2 | {% from "sensu/service_map.jinja" import services with context %}
3 | {% from "sensu/configfile_map.jinja" import files with context %}
4 |
5 | include:
6 | - sensu
7 | - sensu.rabbitmq_conf
8 |
9 | {% if grains['os_family'] == 'Windows' %}
10 | /opt/sensu/bin/sensu-client.xml:
11 | file.managed:
12 | - source: salt://sensu/files/windows/sensu-client.xml
13 | - template: jinja
14 | - require:
15 | - pkg: sensu
16 | sensu_install_dotnet35:
17 | cmd.run:
18 | - name: 'powershell.exe "Import-Module ServerManager;Add-WindowsFeature Net-Framework-Core"'
19 | sensu_enable_windows_service:
20 | cmd.run:
21 | - name: 'sc create sensu-client start= delayed-auto binPath= c:\opt\sensu\bin\sensu-client.exe DisplayName= "Sensu Client"'
22 | - unless: 'sc query sensu-client'
23 | {% endif %}
24 |
25 | {%- if salt['pillar.get']('sensu:standalone_checks') %}
26 | sensu_standalone_checks_file:
27 | file.serialize:
28 | - name: {{ sensu.paths.standalone_checks_file }}
29 | - dataset:
30 | checks: {{ salt['pillar.get']('sensu:standalone_checks') }}
31 | - formatter: json
32 | - require:
33 | - pkg: sensu
34 | - watch_in:
35 | - service: sensu-client
36 | {%- else %}
37 | sensu_standalone_checks_file:
38 | file.absent:
39 | - name: {{ sensu.paths.standalone_checks_file }}
40 | {%- endif %}
41 |
42 | /etc/sensu/conf.d/client.json:
43 | file.serialize:
44 | - formatter: json
45 | - user: {{files.files.user}}
46 | - group: {{files.files.group}}
47 | {% if grains['os_family'] != 'Windows' %}
48 | - mode: 644
49 | {% endif %}
50 | - makedirs: True
51 | - dataset:
52 | client:
53 | name: {{ sensu.client.name }}
54 | address: {{ sensu.client.address }}
55 | subscriptions: {{ sensu.client.subscriptions }}
56 | safe_mode: {{ sensu.client.safe_mode }}
57 | {% if sensu.client.get('keepalive') %}
58 | keepalive: {{ sensu.client.keepalive }}
59 | {% endif %}
60 | {% if sensu.client.get("command_tokens") %}
61 | command_tokens: {{ sensu.client.command_tokens }}
62 | {% endif %}
63 | {% if sensu.client.get("redact") %}
64 | redact: {{ sensu.client.redact }}
65 | {% endif %}
66 | {% if sensu.client.get("override_attributes") %}
67 | {% for attribute, values in sensu.client.override_attributes.items() %}
68 | {{ attribute }}: {{ values|json }}
69 | {% endfor %}
70 | {% endif %}
71 | - require:
72 | - pkg: sensu
73 |
74 | /etc/sensu/plugins:
75 | file.recurse:
76 | - source: salt://{{ sensu.paths.plugins }}
77 | {% if grains['os_family'] != 'Windows' %}
78 | - file_mode: 555
79 | {% endif %}
80 | - require:
81 | - pkg: sensu
82 | - require_in:
83 | - service: sensu-client
84 | - watch_in:
85 | - service: sensu-client
86 |
87 | {% if grains['os_family'] != 'Windows' %}
88 | /etc/default/sensu:
89 | file.replace:
90 | {%- if sensu.client.embedded_ruby %}
91 | - pattern: 'EMBEDDED_RUBY=false'
92 | - repl: 'EMBEDDED_RUBY=true'
93 | {%- else %}
94 | - pattern: 'EMBEDDED_RUBY=true'
95 | - repl: 'EMBEDDED_RUBY=false'
96 | {%- endif %}
97 | - watch_in:
98 | - service: sensu-client
99 | {% endif %}
100 |
101 | {% if sensu.client.nagios_plugins %}
102 | {{ services.nagios_plugins }}:
103 | pkg:
104 | - installed
105 | - require_in:
106 | - service: sensu-client
107 | {% endif %}
108 |
109 | {% set gem_list = salt['pillar.get']('sensu:client:install_gems', []) %}
110 | {% for gem in gem_list %}
111 | {% if gem is mapping %}
112 | {% set gem_name = gem.name %}
113 | {% else %}
114 | {% set gem_name = gem %}
115 | {% endif %}
116 | install_{{ gem_name }}:
117 | gem.installed:
118 | - name: {{ gem_name }}
119 | {% if sensu.client.embedded_ruby %}
120 | - gem_bin: /opt/sensu/embedded/bin/gem
121 | {% endif %}
122 | {% if gem.version is defined %}
123 | - version: {{ gem.version }}
124 | {% endif %}
125 | - rdoc: False
126 | - ri: False
127 | - proxy: {{ salt['pillar.get']('sensu:client:gem_proxy') }}
128 | - source: {{ salt['pillar.get']('sensu:client:gem_source') }}
129 | {% endfor %}
130 |
131 | sensu-client:
132 | service.running:
133 | - enable: True
134 | - require:
135 | - file: /etc/sensu/conf.d/client.json
136 | - file: /etc/sensu/conf.d/rabbitmq.json
137 | - watch:
138 | - file: /etc/sensu/conf.d/*
139 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | sensu-formula
2 | ================
3 |
4 | A saltstack formula to install and configure the open source monitoring framework, [Sensu](http://sensuapp.org/).
5 |
6 | >Note:
7 | See the full [Salt Formulas installation and usage instructions](http://docs.saltstack.com/en/latest/topics/development/conventions/formulas.html). This formula only manages Sensu. You are responsible for installing/configuring RabbitMQ and Redis as appropriate.
8 |
9 | Sensu can be configured/scaled with the individual states installed on multiple servers. All states are configured via the pillar file. Sane defaults are set in pillar_map.jinja and can be over-written in the pillar. The `sensu.client` state currently supports Ubuntu, CentOS and Windows. The `sensu.server`, `sensu.api` and `sensu.uchiwa` states currently support Ubuntu and CentOS.
10 |
11 | Thank you to the SaltStack community for the continued improvement of this formula!
12 |
13 | Available states
14 | ================
15 | * [sensu](#sensu)
16 | * [sensu.server](#sensuserver)
17 | * [sensu.client](#sensuclient)
18 | * [sensu.api](#sensuapi)
19 | * [sensu.uchiwa](#sensuuchiwa)
20 |
21 | Example top.sls:
22 | ```
23 | base:
24 | '*':
25 | - sensu.client
26 |
27 | 'sensu-server-*':
28 | - sensu.server
29 |
30 | 'sensu-api-*':
31 | - sensu.api
32 |
33 | 'uchiwa-*':
34 | - sensu.uchiwa
35 | ```
36 |
37 | `paths`: The `source` path for the handlers/plugins/extentions/etc are configurable in the pillar if you would like to keep these items in a different location. Please note, this directory must be located on the salt master file server and will be prepended with the `salt://` protocol by the formula. If the directory is located on the master in the directory named spam, and is called eggs, the source string is `spam/eggs` and will be converted to `salt://spam/eggs`.
38 |
39 | Backward incompatible changes
40 | =============================
41 |
42 | **2015-04-15:** The default ``sensu:rabbitmq:port`` value is now 5672 (which is the default port of RabbitMQ) instead of 5671. Port 5671 was used to support SSL/TLS as you cannot configure TLS on port 5672.
43 | * If you happened to have used the default previous value of 5671, you should now set it in your pillar file or change your RabbitMQ configuration.
44 | * If you overrode the previous default value of 5671 with 5672, you can now safely remove it.
45 | * If you set up something else instead, you don't have to change anything :)
46 |
47 | **2016-01-08:** The pillar structure for `sensu.uchiwa` has been slightly modified to make it more closely resemble the rendered json and to support multiple users. Please confirm your existing pillar.
48 |
49 | ``sensu``
50 | ------------
51 |
52 | Adds the Sensu repository, and installs the Sensu package.
53 |
54 | Allows the configuration of alternative repositories (i.e. mirrors) using the following syntax:
55 | ```
56 | ## for ubuntu
57 | sensu:
58 | lookup:
59 | repos:
60 | name: deb http://mysensumirror.org/apt/sensu sensu main
61 | key_url: http://mysensumirror.org/apt/sensu/key.gpg
62 |
63 | ## or for RedHat
64 | sensu:
65 | lookup:
66 | repos:
67 | baseurl: http://mysensumirror.org/yum/el/$releasever/$basearch/
68 |
69 | ## or if you don't want the formula to handle the repo for you...
70 | sensu:
71 | lookup:
72 | repos:
73 | enabled: False
74 | ```
75 |
76 |
77 | ``sensu.server``
78 | ------------
79 |
80 | Configures sensu-server and starts the service.
81 |
82 | Requires minimum rabbitmq configuration.
83 | ```
84 | sensu:
85 | rabbitmq:
86 | host: RABBITMQ_HOST_IP (Do not use localhost as the clients also use this.)
87 | user: RABBITMQ_USERNAME
88 | password: RABBITMQ_USER_PASSWORD
89 | ```
90 |
91 | If you use SSL, you must enable it and provide the certs. See the [sensu documentation.](http://sensuapp.org/docs/latest/certificates)
92 |
93 | Custom check definitions/extentions/mutators/handlers/plugins can be deployed to all Sensu servers by placing the scripts into the corresponding directory in ./sensu/files/.
94 |
95 | The included check-procs.rb comes from the [sensu-community-plugins](https://github.com/sensu/sensu-community-plugins) as an example only. There is no guarantee that it up-to-date and it should not be used.
96 |
97 | If you are not running your redis server locally, set the following in the pillar:
98 | ```
99 | sensu:
100 | redis:
101 | host: HOSTNAME
102 | port: PORT
103 | password: PASSWORD # Optional
104 | ```
105 |
106 | If you are adding handlers which have additional gem dependencies, i.e the [mailer](https://github.com/sensu/sensu-community-plugins/blob/master/handlers/notification/mailer.rb) handler. You can add them to the pillar data and they will be installed on your Sensu servers.
107 | ```
108 | sensu:
109 | server:
110 | install_gems:
111 | - mail
112 | - timeout
113 | ```
114 |
115 | ``sensu.client``
116 | ------------
117 |
118 | Configures sensu-client and starts the service.
119 |
120 | Check scripts can be deployed to all clients by placing them into ./sensu/files/plugins.
121 |
122 | You can use the embedded ruby, set a proxy or mirror for installing gems, or install nagios plugins by setting:
123 | ```
124 | sensu:
125 | client:
126 | embedded_ruby: true
127 | nagios_plugins: true
128 | gem_source: http://gemmirror.example.com:9292
129 | # or
130 | gem_proxy: http://squid.example.com:3128
131 | ```
132 |
133 | To subscribe your clients to the appropriate checks, you can update the `sensu` pillar with the required subscriptions. You can also override the client address to another interface or change the name of the client. In addition, you can also enable Sensu's safe mode (highly recommended, off by default).
134 |
135 | ```
136 | sensu:
137 | client:
138 | name: {{ grains['sensu_id'] }}
139 | address: {{ grains['ip4_interfaces']['eth0'][0] }}
140 | subscriptions: ['linux', 'compute']
141 | ```
142 |
143 | If you would like to use [command tokens](https://sensuapp.org/docs/latest/checks#example-check-command-tokens) in your checks you can add a section under client as shown here:
144 |
145 | ```
146 | sensu:
147 | client:
148 | command_tokens:
149 | disk:
150 | warning: 97
151 | critical: 99
152 | ```
153 |
154 | If you would like to use the [redact](https://sensuapp.org/docs/latest/clients) feature in your checks you can add a section under client as shown here:
155 |
156 | ```
157 | sensu:
158 | client:
159 | redact:
160 | - password
161 | ```
162 |
163 | This will redact any command token value who's key is defined as "password" from check configurations and logs. Command token substitution should be used in check configurations when redacting sensitive information such as passwords.
164 |
165 | If you are adding plugins/checks which have additional gem dependencies. You can add them to the pillar data and they will be installed on your Sensu clients.
166 | ```
167 | sensu:
168 | client:
169 | install_gems:
170 | - libxml-xmlrpc
171 | ```
172 |
173 |
174 | ``sensu.api``
175 | ------------
176 |
177 | Configures sensu-api and starts the service.
178 |
179 |
180 |
181 | ``sensu.uchiwa``
182 | ------------
183 | >Note: The Uchiwa pillar structure has changed! If you have previously used this state and are potentially upgrading, please take a minute to review.
184 |
185 | Configures [uchiwa](http://docs.uchiwa.io/en/latest/) and starts the service. The pillar defaults are located in the ```pillar_map.jinja```.
186 |
187 | The state now supports [multiple users with simple authentication](http://docs.uchiwa.io/en/latest/configuration/uchiwa/#multiple-users-with-simple-authentication). If you are upgrading from a previous version of this state, you will need make some minor modifications to your pillar.
188 |
189 | **Site and user definitions**
190 | ``` yaml
191 | # new style users and sites
192 | sensu:
193 | uchiwa:
194 | users:
195 | - username: bobby
196 | password: secret
197 | role: { readonly: False }
198 | sites:
199 | - name: 'Site 1'
200 | host: '1.1.1.1'
201 | user: 'bobby'
202 | pass: secret
203 | - name: 'Site 2'
204 | host: localhost
205 | user: nicky
206 | pass: secret
207 | ssl: True
208 | ```
209 |
--------------------------------------------------------------------------------
/sensu/files/plugins/check-procs.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Check Procs
4 | # ===
5 | #
6 | # Finds processes matching various filters (name, state, etc). Will not
7 | # match itself by default. The number of processes found will be tested
8 | # against the Warning/critical thresholds. By default, fails with a
9 | # CRITICAL if more than one process matches -- you must specify values
10 | # for -w and -c to override this.
11 | #
12 | # Attempts to work on Cygwin (where ps does not have the features we
13 | # need) by calling Windows' tasklist.exe, but this is not well tested.
14 | #
15 | # Examples:
16 | #
17 | # # chef-client is running
18 | # check-procs -p chef-client -W 1
19 | #
20 | # # there are not too many zombies
21 | # check-procs -s Z -w 5 -c 10
22 | #
23 | # Copyright 2011 Sonian, Inc
24 | #
25 | # Released under the same terms as Sensu (the MIT license); see LICENSE
26 | # for details.
27 |
28 | require 'rubygems' if RUBY_VERSION < '1.9.0'
29 | require 'sensu-plugin/check/cli'
30 |
31 | class CheckProcs < Sensu::Plugin::Check::CLI
32 |
33 | option :warn_over,
34 | :short => '-w N',
35 | :long => '--warn-over N',
36 | :description => 'Trigger a warning if over a number',
37 | :proc => proc {|a| a.to_i }
38 |
39 | option :crit_over,
40 | :short => '-c N',
41 | :long => '--critical-over N',
42 | :description => 'Trigger a critical if over a number',
43 | :proc => proc {|a| a.to_i }
44 |
45 | option :warn_under,
46 | :short => '-W N',
47 | :long => '--warn-under N',
48 | :description => 'Trigger a warning if under a number',
49 | :proc => proc {|a| a.to_i },
50 | :default => 1
51 |
52 | option :crit_under,
53 | :short => '-C N',
54 | :long => '--critical-under N',
55 | :description => 'Trigger a critial if under a number',
56 | :proc => proc {|a| a.to_i },
57 | :default => 1
58 |
59 | option :metric,
60 | :short => '-t METRIC',
61 | :long => '--metric METRIC',
62 | :description => 'Trigger a critical if there are METRIC procs',
63 | :proc => proc {|a| a.to_sym }
64 |
65 | option :match_self,
66 | :short => '-m',
67 | :long => '--match-self',
68 | :description => 'Match itself',
69 | :boolean => true,
70 | :default => false
71 |
72 | option :match_parent,
73 | :short => '-M',
74 | :long => '--match-parent',
75 | :description => 'Match parent process it uses ruby {process.ppid}',
76 | :boolean => true,
77 | :default => false
78 |
79 | option :cmd_pat,
80 | :short => '-p PATTERN',
81 | :long => '--pattern PATTERN',
82 | :description => 'Match a command against this pattern'
83 |
84 | option :file_pid,
85 | :short => '-f PID',
86 | :long => '--file-pid PID',
87 | :description => 'Check against a specific PID'
88 |
89 | option :vsz,
90 | :short => '-z VSZ',
91 | :long => '--virtual-memory-size VSZ',
92 | :description => 'Trigger on a Virtual Memory size is bigger than this',
93 | :proc => proc {|a| a.to_i }
94 |
95 | option :rss,
96 | :short => '-r RSS',
97 | :long => '--resident-set-size RSS',
98 | :description => 'Trigger on a Resident Set size is bigger than this',
99 | :proc => proc {|a| a.to_i }
100 |
101 | option :pcpu,
102 | :short => '-P PCPU',
103 | :long => '--proportional-set-size PCPU',
104 | :description => 'Trigger on a Proportional Set Size is bigger than this',
105 | :proc => proc {|a| a.to_f }
106 |
107 | option :thcount,
108 | :short => '-T THCOUNT',
109 | :long => '--thread-count THCOUNT',
110 | :description => 'Trigger on a Thread Count is bigger than this',
111 | :proc => proc {|a| a.to_i }
112 |
113 | option :state,
114 | :short => '-s STATE',
115 | :long => '--state STATE',
116 | :description => 'Trigger on a specific state, example: Z for zombie',
117 | :proc => proc {|a| a.split(',') }
118 |
119 | option :user,
120 | :short => '-u USER',
121 | :long => '--user USER',
122 | :description => 'Trigger on a specific user',
123 | :proc => proc {|a| a.split(',') }
124 |
125 | option :esec_over,
126 | :short => '-e SECONDS',
127 | :long => '--esec-over SECONDS',
128 | :proc => proc {|a| a.to_i },
129 | :description => 'Match processes that older that this, in SECONDS'
130 |
131 | option :esec_under,
132 | :short => '-E SECONDS',
133 | :long => '--esec-under SECONDS',
134 | :proc => proc {|a| a.to_i },
135 | :description => 'Match process that are younger than this, in SECONDS'
136 |
137 | option :cpu_over,
138 | :short => '-i SECONDS',
139 | :long => '--cpu-over SECONDS',
140 | :proc => proc {|a| a.to_i },
141 | :description => 'Match processes cpu time that is older than this, in SECONDS'
142 |
143 | option :cpu_under,
144 | :short => '-I SECONDS',
145 | :long => '--cpu-under SECONDS',
146 | :proc => proc {|a| a.to_i },
147 | :description => 'Match processes cpu time that is younger than this, in SECONDS'
148 |
149 | def read_pid(path)
150 | if File.exists?(path)
151 | File.read(path).strip.to_i
152 | else
153 | unknown "Could not read pid file #{path}"
154 | end
155 | end
156 |
157 | def read_lines(cmd)
158 | IO.popen(cmd + ' 2>&1') do |child|
159 | child.read.split("\n")
160 | end
161 | end
162 |
163 | def line_to_hash(line, *cols)
164 | Hash[cols.zip(line.strip.split(/\s+/, cols.size))]
165 | end
166 |
167 | def on_cygwin?
168 | `ps -W 2>&1`; $?.exitstatus == 0
169 | end
170 |
171 | def get_procs
172 | if on_cygwin?
173 | read_lines('ps -aWl').drop(1).map do |line|
174 | # Horrible hack because cygwin's ps has no o option, every
175 | # format includes the STIME column (which may contain spaces),
176 | # and the process state (which isn't actually a column) can be
177 | # blank. As of revision 1.35, the format is:
178 | # const char *lfmt = "%c %7d %7d %7d %10u %4s %4u %8s %s\n";
179 | state = line.slice!(0..0)
180 | _stime = line.slice!(45..53)
181 | line_to_hash(line, :pid, :ppid, :pgid, :winpid, :tty, :uid, :etime, :command, :time).merge(:state => state)
182 | end
183 | else
184 | read_lines('ps axwwo user,pid,vsz,rss,pcpu,nlwp,state,etime,time,command').drop(1).map do |line|
185 | line_to_hash(line, :user, :pid, :vsz, :rss, :pcpu, :thcount, :state, :etime, :time, :command)
186 | end
187 | end
188 | end
189 |
190 | def etime_to_esec(etime)
191 | m = /(\d+-)?(\d\d:)?(\d\d):(\d\d)/.match(etime)
192 | (m[1]||0).to_i*86400 + (m[2]||0).to_i*3600 + (m[3]||0).to_i*60 + (m[4]||0).to_i
193 | end
194 |
195 | def cputime_to_csec(time)
196 | m = /(\d+-)?(\d\d:)?(\d\d):(\d\d)/.match(time)
197 | (m[1]||0).to_i*86400 + (m[2]||0).to_i*3600 + (m[3]||0).to_i*60 + (m[4]||0).to_i
198 | end
199 |
200 | def run
201 | procs = get_procs
202 |
203 | if config[:file_pid] && (file_pid = read_pid(config[:file_pid]))
204 | procs.reject! { |p| p[:pid].to_i != file_pid }
205 | end
206 | procs.reject! {|p| p[:pid].to_i == $$ } unless config[:match_self]
207 | procs.reject! {|p| p[:pid].to_i == Process.ppid } unless config[:match_parent]
208 | procs.reject! {|p| p[:command] !~ /#{config[:cmd_pat]}/ } if config[:cmd_pat]
209 | procs.reject! {|p| p[:vsz].to_f > config[:vsz] } if config[:vsz]
210 | procs.reject! {|p| p[:rss].to_f > config[:rss] } if config[:rss]
211 | procs.reject! {|p| p[:pcpu].to_f > config[:pcpu] } if config[:pcpu]
212 | procs.reject! {|p| p[:thcount].to_i > config[:thcount] } if config[:thcount]
213 | procs.reject! {|p| etime_to_esec(p[:etime]) >= config[:esec_under] } if config[:esec_under]
214 | procs.reject! {|p| etime_to_esec(p[:etime]) <= config[:esec_over] } if config[:esec_over]
215 | procs.reject! {|p| cputime_to_csec(p[:time]) >= config[:cpu_under] } if config[:cpu_under]
216 | procs.reject! {|p| cputime_to_csec(p[:time]) <= config[:cpu_over] } if config[:cpu_over]
217 | procs.reject! {|p| !config[:state].include?(p[:state]) } if config[:state]
218 | procs.reject! {|p| !config[:user].include?(p[:user]) } if config[:user]
219 |
220 | msg = "Found #{procs.size} matching processes"
221 | msg += "; cmd /#{config[:cmd_pat]}/" if config[:cmd_pat]
222 | msg += "; state #{config[:state].join(',')}" if config[:state]
223 | msg += "; user #{config[:user].join(',')}" if config[:user]
224 | msg += "; vsz < #{config[:vsz]}" if config[:vsz]
225 | msg += "; rss < #{config[:rss]}" if config[:rss]
226 | msg += "; pcpu < #{config[:pcpu]}" if config[:pcpu]
227 | msg += "; thcount < #{config[:thcount]}" if config[:thcount]
228 | msg += "; esec < #{config[:esec_under]}" if config[:esec_under]
229 | msg += "; esec > #{config[:esec_over]}" if config[:esec_over]
230 | msg += "; csec < #{config[:cpu_under]}" if config[:cpu_under]
231 | msg += "; csec > #{config[:cpu_over]}" if config[:cpu_over]
232 | msg += "; pid #{config[:file_pid]}" if config[:file_pid]
233 |
234 | if config[:metric]
235 | count = procs.map {|p| p[config[:metric]].to_i }.reduce {|a, b| a + b }
236 | msg += "; #{config[:metric]} == #{count}"
237 | else
238 | count = procs.size
239 | end
240 |
241 | if !!config[:crit_under] && count < config[:crit_under]
242 | critical msg
243 | elsif !!config[:crit_over] && count > config[:crit_over]
244 | critical msg
245 | elsif !!config[:warn_under] && count < config[:warn_under]
246 | warning msg
247 | elsif !!config[:warn_over] && count > config[:warn_over]
248 | warning msg
249 | else
250 | ok msg
251 | end
252 | end
253 |
254 | end
255 |
--------------------------------------------------------------------------------