├── 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 | --------------------------------------------------------------------------------