Bläddra i källkod

Merge pull request #213 from michaelpporter/client_templates

Templates in client_definitions
Jared 6 år sedan
förälder
incheckning
b1d838d4de

+ 2 - 1
.gitignore

@@ -1,3 +1,4 @@
 site
 site
-molecule/default/data/
+molecule/shared/data/*
+!molecule/shared/data/static/
 molecule/*/cache/
 molecule/*/cache/

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ This project adheres to [Semantic Versioning](http://semver.org/)
 The format is based on [Keep a Changelog](http://keepachangelog.com/).
 The format is based on [Keep a Changelog](http://keepachangelog.com/).
 
 
 ## [Unreleased]
 ## [Unreleased]
+- Add `client_templates` option for group based tempaltes (@michaelpporter)
+- Add `run_once: true` to `delegate_to: localhost` (@michaelpporter)
 
 
 ## [5.0.0] - 2019-02-19
 ## [5.0.0] - 2019-02-19
 ### Breaking Changes
 ### Breaking Changes

+ 61 - 7
docs/dynamic_checks.md

@@ -15,12 +15,14 @@ data/static
     |-- handlers
     |-- handlers
     `-- mutators
     `-- mutators
 ```
 ```
-With all the checks and definitions dropped in, the static data store might look something like this:
+With all the checks (check commands) and definitions (JSON check files) dropped in, the static data store might look something like this:
 ```
 ```
 $ tree data/static
 $ tree data/static
 data/static
 data/static
 `-- sensu
 `-- sensu
     |-- checks
     |-- checks
+    |   |-- mysql
+    |   |   `-- check_mysql.sh
     |   |-- sensu_rabbitmq_servers
     |   |-- sensu_rabbitmq_servers
     |   |   `-- check_rabbitmq.sh
     |   |   `-- check_rabbitmq.sh
     |   |-- sensu_redis_servers
     |   |-- sensu_redis_servers
@@ -42,6 +44,11 @@ data/static
     |   `-- smartos_check_mem.json.j2
     |   `-- smartos_check_mem.json.j2
     |-- client_definitions
     |-- client_definitions
     |   |-- sensu_rabbitmq_servers
     |   |-- sensu_rabbitmq_servers
+    |   |   `-- check_users.json
+    |   `-- webservers
+    |       `-- check_uptime.json
+    |-- client_templates
+    |   |-- mysql
     |   |   `-- check_users.json.j2
     |   |   `-- check_users.json.j2
     |   `-- webservers
     |   `-- webservers
     |       `-- check_uptime.json.j2
     |       `-- check_uptime.json.j2
@@ -49,7 +56,7 @@ data/static
     |   `-- pushover.rb
     |   `-- pushover.rb
     `-- mutators
     `-- mutators
 ```
 ```
-As you can see, in the `sensu/checks` directory, there are the `sensu_rabbitmq_servers`, `sensu_redis_servers`, `webservers` & `zones` subdirectories.
+As you can see, in the `sensu/checks` directory, there are the `mysql`, `sensu_rabbitmq_servers`, `sensu_redis_servers`, `webservers` & `zones` subdirectories.
 If you've had a peruse through some of the other documentation here, you'll know that these groups are defined within my Ansible inventory:
 If you've had a peruse through some of the other documentation here, you'll know that these groups are defined within my Ansible inventory:
 ``` ini
 ``` ini
 [sensu_rabbitmq_servers]
 [sensu_rabbitmq_servers]
@@ -69,6 +76,9 @@ tater.cmacr.ae
 beardy.cmacr.ae
 beardy.cmacr.ae
 pkgsrc.cmacr.ae
 pkgsrc.cmacr.ae
 
 
+[mysql]
+mysql.cmacr.ae
+
 [zones]
 [zones]
 ansible.cmacr.ae
 ansible.cmacr.ae
 beardy.cmacr.ae
 beardy.cmacr.ae
@@ -83,6 +93,7 @@ sensu.cmacr.ae
 tater.cmacr.ae
 tater.cmacr.ae
 web.cmacr.ae
 web.cmacr.ae
 test.cmacr.ae
 test.cmacr.ae
+mysql.cmacr.ae
 ```
 ```
 Under these subdirectories, you can see [checks](https://docs.sensu.io/sensu-core/latest/reference/checks/) that relate to the directory they're placed in.
 Under these subdirectories, you can see [checks](https://docs.sensu.io/sensu-core/latest/reference/checks/) that relate to the directory they're placed in.
 For example, our `webservers` subdirectory includes a `check_nginx.sh` script, whilst the `sensu_rabbitmq_servers` subdirectory has one that most likely checks for RabbitMQ problems (it does... trust me).
 For example, our `webservers` subdirectory includes a `check_nginx.sh` script, whilst the `sensu_rabbitmq_servers` subdirectory has one that most likely checks for RabbitMQ problems (it does... trust me).
@@ -110,26 +121,69 @@ This will [register](https://docs.ansible.com/ansible/latest/user_guide/playbook
 
 
 And, because nodes can of course be members of more than just one group, checks will be deployed in full to nodes that belong to several groups!
 And, because nodes can of course be members of more than just one group, checks will be deployed in full to nodes that belong to several groups!
 
 
-Additionally, standalone checks can be distributed to hosts based on group membership. These definitions are located in the client_definitions folder. These will be deployed to the configuration directory of the clients.
+Additionally, standalone checks and subscription cheecks (if your client is in [safe_mode](https://docs.sensu.io/sensu-core/latest/reference/clients/)), can be distributed to hosts based on group membership. These definitions are located in the `client_definitions` folder. These will be deployed to the configuration directory of the clients.
 
 
 These are deployed with the following pair of plays, also in the `tasks/plugins.yml` playbook:
 These are deployed with the following pair of plays, also in the `tasks/plugins.yml` playbook:
 ``` yaml
 ``` yaml
 - name: Register available client definitions
 - name: Register available client definitions
-  local_action: command ls {{ static_data_store }}/sensu/client_definitions
+  command: "ls {{ static_data_store }}/sensu/client_definitions"
+  delegate_to: localhost
   register: sensu_available_client_definitions
   register: sensu_available_client_definitions
   changed_when: false
   changed_when: false
   become: false
   become: false
+  run_once: true
 
 
 - name: Deploy client definitions
 - name: Deploy client definitions
   copy:
   copy:
     src: "{{ static_data_store }}/sensu/client_definitions/{{ item }}/"
     src: "{{ static_data_store }}/sensu/client_definitions/{{ item }}/"
-    dest: "{{ sensu_config_path }}/conf.d/{{ item | basename | regex_replace('.j2', '')}}"
-    mode: 0755
+    dest: "{{ sensu_config_path }}/conf.d/{{ item | basename | regex_replace('.j2', '') }}"
+    owner: "{{ sensu_user_name }}"
+    group: "{{ sensu_group_name }}"
+  when:
+    - sensu_available_client_definitions is defined
+    - sensu_available_client_definitions is not skipped
+    - item in sensu_available_client_definitions.stdout_lines
+  loop: "{{ group_names|flatten }}"
+  notify: restart sensu-client service
+```
+
+If you are using templates for your, standalone checks and subscription cheecks can be distributed from the  `client_templates` folder. These will be deployed to the configuration directory of the clients. **Note** if you have a matching folder/file name in the `client_definitions` they will be overwritten by the template.
+
+These are deployed with the following set of plays, also in the `tasks/plugins.yml` playbook:
+
+``` yaml
+- name: Register available client templates
+  command: "ls {{ static_data_store }}/sensu/client_templates"
+  delegate_to: localhost
+  register: sensu_available_client_templates
+  changed_when: false
+  become: false
+  run_once: true
+
+- name: Deploy client template folders
+  file:
+    path: '{{ sensu_config_path }}/conf.d/{{ item | basename }}'
+    state: directory
     owner: "{{ sensu_user_name }}"
     owner: "{{ sensu_user_name }}"
     group: "{{ sensu_group_name }}"
     group: "{{ sensu_group_name }}"
-  when: "sensu_available_client_definitions is defined and item in sensu_available_client_definitions.stdout_lines"
+  when:
+    - sensu_available_client_templates is defined
+    - sensu_available_client_templates is not skipped
+    - item in sensu_available_client_templates.stdout_lines
   loop: "{{ group_names|flatten }}"
   loop: "{{ group_names|flatten }}"
   notify: restart sensu-client service
   notify: restart sensu-client service
+
+- name: Deploy client templates
+  template:
+    src: "{{ static_data_store }}/sensu/client_templates/{{ item.path | dirname }}/{{ item.path | basename }}"
+    dest: "{{ sensu_config_path }}/conf.d/{{ item.path | dirname }}/{{ item.path | basename | regex_replace('.j2', '') }}"
+    owner: "{{ sensu_user_name }}"
+    group: "{{ sensu_group_name }}"
+  with_filetree: "{{ static_data_store }}/sensu/client_templates"
+  when:
+    - item.state == 'file'
+    - item.path | dirname in group_names
+  notify: restart sensu-client service
 ```
 ```
 
 
 ## Picking up changes on the fly
 ## Picking up changes on the fly

+ 14 - 5
docs/index.md

@@ -1,7 +1,9 @@
 # Ansible Sensu [![Ansible Galaxy](https://img.shields.io/badge/galaxy-sensu.sensu-660198.svg?style=flat)](https://galaxy.ansible.com/sensu/sensu/)
 # Ansible Sensu [![Ansible Galaxy](https://img.shields.io/badge/galaxy-sensu.sensu-660198.svg?style=flat)](https://galaxy.ansible.com/sensu/sensu/)
+
 An [Ansible](https://ansible.com) role that deploys a full [Sensu](https://sensuapp.org) stack, a modern, open source monitoring framework.
 An [Ansible](https://ansible.com) role that deploys a full [Sensu](https://sensuapp.org) stack, a modern, open source monitoring framework.
 
 
 ## Features
 ## Features
+
 - Deploy a full [Sensu](https://sensu.io) stack, including RabbitMQ, redis, and the [Uchiwa dashboard](https://uchiwa.io/)
 - Deploy a full [Sensu](https://sensu.io) stack, including RabbitMQ, redis, and the [Uchiwa dashboard](https://uchiwa.io/)
 - Tight integration with the Ansible inventory - deployment of monitoring checks based on inventory grouping
 - Tight integration with the Ansible inventory - deployment of monitoring checks based on inventory grouping
 - Fine grained control over dynamic client configurations
 - Fine grained control over dynamic client configurations
@@ -14,9 +16,11 @@ Along with deploying the Sensu Server, API and clients, this role can deploy a f
 However, if you want to rely on other roles/management methods to deploy/manage these services, [it's nice and easy to integrate this role](integration/).
 However, if you want to rely on other roles/management methods to deploy/manage these services, [it's nice and easy to integrate this role](integration/).
 
 
 ## Requirements
 ## Requirements
+
 This role requires Ansible 2.5
 This role requires Ansible 2.5
 
 
 ## Supported Platforms
 ## Supported Platforms
+
 ### Automatically tested via TravisCI
 ### Automatically tested via TravisCI
 
 
 - [CentOS - 6](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS6.9)
 - [CentOS - 6](https://wiki.centos.org/Manuals/ReleaseNotes/CentOS6.9)
@@ -32,15 +36,18 @@ This role requires Ansible 2.5
 - [Amazon Linux 2](https://aws.amazon.com/amazon-linux-2/)
 - [Amazon Linux 2](https://aws.amazon.com/amazon-linux-2/)
 
 
 ### Supported manually (compatibility not always guaranteed)
 ### Supported manually (compatibility not always guaranteed)
+
 - [SmartOS - base-64 15.x.x](https://docs.joyent.com/images/smartos/base#version-15xx)
 - [SmartOS - base-64 15.x.x](https://docs.joyent.com/images/smartos/base#version-15xx)
 - [FreeBSD - 10.3, 11.0 (64-bit only)](https://www.freebsd.org/releases/10.2R/relnotes.html)
 - [FreeBSD - 10.3, 11.0 (64-bit only)](https://www.freebsd.org/releases/10.2R/relnotes.html)
 - [OpenBSD - 6.2](https://www.openbsd.org/62.html)
 - [OpenBSD - 6.2](https://www.openbsd.org/62.html)
 
 
 ## Role Variables
 ## Role Variables
+
 All variables have sensible defaults, which can be found in `defaults/main.yml`.
 All variables have sensible defaults, which can be found in `defaults/main.yml`.
 Head over to [the role variables page](role_variables.md) to review them
 Head over to [the role variables page](role_variables.md) to review them
 
 
 ## Install (Ansible Galaxy)
 ## Install (Ansible Galaxy)
+
 To install this role from [Ansible Galaxy](https://galaxy.ansible.com), simpy run:
 To install this role from [Ansible Galaxy](https://galaxy.ansible.com), simpy run:
 `ansible-galaxy install sensu.sensu`
 `ansible-galaxy install sensu.sensu`
 
 
@@ -51,6 +58,7 @@ To install this role from [Ansible Galaxy](https://galaxy.ansible.com), simpy ru
     roles:
     roles:
       - role: sensu.sensu
       - role: sensu.sensu
 ```
 ```
+
 Or, passing parameter values:
 Or, passing parameter values:
 
 
 ``` yaml
 ``` yaml
@@ -58,19 +66,20 @@ Or, passing parameter values:
     roles:
     roles:
       - role: sensu.sensu
       - role: sensu.sensu
         sensu_master: true
         sensu_master: true
-        sensu_include_dashboard: true 
+        sensu_include_dashboard: true
 ```
 ```
 
 
-License
--------
+## License
+
 [MIT](license.md)
 [MIT](license.md)
 
 
-Author Information
-------------------
+## Author Information
+
 Originally created by [Calum MacRae](http://cmacr.ae)
 Originally created by [Calum MacRae](http://cmacr.ae)
 Supported by the [Sensu Community Ansible Maintainers](https://github.com/sensu-plugins/community/#maintained-areas)
 Supported by the [Sensu Community Ansible Maintainers](https://github.com/sensu-plugins/community/#maintained-areas)
 
 
 ### Contributors
 ### Contributors
+
 See the projects [Contributors page](https://github.com/sensu/sensu-ansible/graphs/contributors)
 See the projects [Contributors page](https://github.com/sensu/sensu-ansible/graphs/contributors)
 
 
 Feel free to:
 Feel free to:

+ 7 - 0
molecule/amazonlinux/molecule.yml

@@ -13,11 +13,15 @@ platforms:
     command: /sbin/init
     command: /sbin/init
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
   - name: amazonlinux-2
   - name: amazonlinux-2
     image: dokken/amazonlinux-2
     image: dokken/amazonlinux-2
     command: /usr/lib/systemd/systemd
     command: /usr/lib/systemd/systemd
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
 provisioner:
 provisioner:
@@ -51,6 +55,9 @@ provisioner:
         sensu_api_host: "{{ ansible_hostname }}"
         sensu_api_host: "{{ ansible_hostname }}"
         ansible_default_ipv4:
         ansible_default_ipv4:
           address: 127.0.0.1
           address: 127.0.0.1
+        sensu_remote_plugins:
+          - sensu-plugins-disk-checks
+        sensu_check_interval: 60
     host_vars:
     host_vars:
       amazonlinux-1:
       amazonlinux-1:
         inspec_version: el6
         inspec_version: el6

+ 7 - 0
molecule/centos/molecule.yml

@@ -13,11 +13,15 @@ platforms:
     command: /sbin/init
     command: /sbin/init
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
   - name: centos-7
   - name: centos-7
     image: dokken/centos-7
     image: dokken/centos-7
     command: /usr/lib/systemd/systemd
     command: /usr/lib/systemd/systemd
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
 provisioner:
 provisioner:
@@ -51,6 +55,9 @@ provisioner:
         sensu_api_host: "{{ ansible_hostname }}"
         sensu_api_host: "{{ ansible_hostname }}"
         ansible_default_ipv4:
         ansible_default_ipv4:
           address: 127.0.0.1
           address: 127.0.0.1
+        sensu_remote_plugins:
+          - sensu-plugins-disk-checks
+        sensu_check_interval: 60
     host_vars:
     host_vars:
       centos-6:
       centos-6:
         inspec_version: el6
         inspec_version: el6

+ 7 - 0
molecule/debian/molecule.yml

@@ -14,6 +14,8 @@ platforms:
     privileged: yes
     privileged: yes
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    groups:
+      - sensu_checks
   - name: debian-9
   - name: debian-9
     image: dokken/debian-9
     image: dokken/debian-9
     command: /lib/systemd/systemd
     command: /lib/systemd/systemd
@@ -21,6 +23,8 @@ platforms:
       - SYS_ADMIN
       - SYS_ADMIN
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    groups:
+      - sensu_checks
 provisioner:
 provisioner:
   name: ansible
   name: ansible
   config_options:
   config_options:
@@ -52,6 +56,9 @@ provisioner:
         sensu_api_host: "{{ ansible_hostname }}"
         sensu_api_host: "{{ ansible_hostname }}"
         ansible_default_ipv4:
         ansible_default_ipv4:
           address: 127.0.0.1
           address: 127.0.0.1
+        sensu_remote_plugins:
+          - sensu-plugins-disk-checks
+        sensu_check_interval: 60
     host_vars:
     host_vars:
       debian-8:
       debian-8:
         inspec_version: ubuntu1604
         inspec_version: ubuntu1604

+ 9 - 0
molecule/fedora/molecule.yml

@@ -15,6 +15,8 @@ platforms:
       - SYS_ADMIN
       - SYS_ADMIN
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    groups:
+      - sensu_checks
   - name: fedora-27
   - name: fedora-27
     image: dokken/fedora-27
     image: dokken/fedora-27
     command: /usr/lib/systemd/systemd
     command: /usr/lib/systemd/systemd
@@ -22,6 +24,8 @@ platforms:
       - SYS_ADMIN
       - SYS_ADMIN
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    groups:
+      - sensu_checks
   - name: fedora-28
   - name: fedora-28
     image: dokken/fedora-latest
     image: dokken/fedora-latest
     command: /usr/lib/systemd/systemd
     command: /usr/lib/systemd/systemd
@@ -29,6 +33,8 @@ platforms:
       - SYS_ADMIN
       - SYS_ADMIN
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
+    groups:
+      - sensu_checks
 provisioner:
 provisioner:
   name: ansible
   name: ansible
   config_options:
   config_options:
@@ -60,6 +66,9 @@ provisioner:
         sensu_api_host: "{{ ansible_hostname }}"
         sensu_api_host: "{{ ansible_hostname }}"
         ansible_default_ipv4:
         ansible_default_ipv4:
           address: 127.0.0.1
           address: 127.0.0.1
+        sensu_remote_plugins:
+          - sensu-plugins-disk-checks
+        sensu_check_interval: 60
     host_vars:
     host_vars:
       fedora-26:
       fedora-26:
         inspec_version: el7
         inspec_version: el7

+ 11 - 0
molecule/shared/data/static/sensu/client_definitions/sensu_masters/check_disk_usage.json

@@ -0,0 +1,11 @@
+{
+    "checks": {
+        "check_disk_usage": {
+            "command": "check-disk-usage.rb",
+            "standalone": true,
+            "ttl": 1800,
+            "interval": 120,
+            "refresh": 1800
+        }
+    }
+}

+ 11 - 0
molecule/shared/data/static/sensu/client_templates/not_used/not_a_check.json.j2

@@ -0,0 +1,11 @@
+{
+    "checks": {
+        "not_a_check": {
+            "command": "check-disk-usage.rb",
+            "standalone": true,
+            "ttl": 1800,
+            "interval": {{ sensu_check_interval }},
+            "refresh": 1800
+        }
+    }
+}

+ 11 - 0
molecule/shared/data/static/sensu/client_templates/sensu_checks/metrics_disk_usage.json.j2

@@ -0,0 +1,11 @@
+{
+    "checks": {
+        "metrics_disk_usage": {
+            "command": "metrics-disk-usage.rb",
+            "standalone": true,
+            "ttl": 1800,
+            "interval": {{ sensu_check_interval }},
+            "refresh": 1800
+        }
+    }
+}

+ 19 - 0
molecule/shared/tests/test_default.rb

@@ -50,3 +50,22 @@ describe http('http://127.0.0.1:4567/health',
               params: { consumers: 1 }) do
               params: { consumers: 1 }) do
   its('status') { should eq 204 }
   its('status') { should eq 204 }
 end
 end
+
+# Ensure disk check exists
+describe json('/etc/sensu/conf.d/sensu_masters/check_disk_usage.json') do
+  its(%w[checks check_disk_usage command]) \
+    { should eq 'check-disk-usage.rb' }
+  its(%w[checks check_disk_usage interval]) { should eq 120 }
+end
+
+# Ensure disk metrics exists
+describe json('/etc/sensu/conf.d/sensu_checks/metrics_disk_usage.json') do
+  its(%w[checks metrics_disk_usage command]) \
+    { should eq 'metrics-disk-usage.rb' }
+  its(%w[checks metrics_disk_usage interval]) { should eq 60 }
+end
+
+# Ensure not_used does not exist
+describe file('/etc/sensu/conf.d/not_used/not_a_check.json') do
+  it { should_not exist }
+end

+ 9 - 0
molecule/ubuntu/molecule.yml

@@ -13,11 +13,15 @@ platforms:
     command: /sbin/init
     command: /sbin/init
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
   - name: ubuntu-16.04
   - name: ubuntu-16.04
     image: dokken/ubuntu-16.04
     image: dokken/ubuntu-16.04
     command: /bin/systemd
     command: /bin/systemd
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
   - name: ubuntu-18.04
   - name: ubuntu-18.04
@@ -25,6 +29,8 @@ platforms:
     command: /bin/systemd
     command: /bin/systemd
     capabilities:
     capabilities:
       - SYS_ADMIN
       - SYS_ADMIN
+    groups:
+      - sensu_checks
     volumes:
     volumes:
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
       - /sys/fs/cgroup:/sys/fs/cgroup:ro
 provisioner:
 provisioner:
@@ -58,6 +64,9 @@ provisioner:
         sensu_api_host: "{{ ansible_hostname }}"
         sensu_api_host: "{{ ansible_hostname }}"
         ansible_default_ipv4:
         ansible_default_ipv4:
           address: 127.0.0.1
           address: 127.0.0.1
+        sensu_remote_plugins:
+          - sensu-plugins-disk-checks
+        sensu_check_interval: 60
     host_vars:
     host_vars:
       ubuntu-14.04:
       ubuntu-14.04:
         inspec_version: ubuntu1404
         inspec_version: ubuntu1404

+ 38 - 1
tasks/plugins.yml

@@ -18,6 +18,7 @@
     dest: "{{ static_data_store }}/sensu/{{ item }}"
     dest: "{{ static_data_store }}/sensu/{{ item }}"
   delegate_to: localhost
   delegate_to: localhost
   become: no
   become: no
+  run_once: true
   loop:
   loop:
     - checks
     - checks
     - filters
     - filters
@@ -25,6 +26,7 @@
     - mutators
     - mutators
     - definitions
     - definitions
     - client_definitions
     - client_definitions
+    - client_templates
 
 
 - name: Ensure any remote plugins defined are present
 - name: Ensure any remote plugins defined are present
   shell: umask 0022; sensu-install -p {{ item }}
   shell: umask 0022; sensu-install -p {{ item }}
@@ -38,6 +40,7 @@
   register: sensu_available_checks
   register: sensu_available_checks
   changed_when: false
   changed_when: false
   become: false
   become: false
+  run_once: true
 
 
 - name: Deploy check plugins
 - name: Deploy check plugins
   copy:
   copy:
@@ -100,6 +103,7 @@
   register: sensu_available_client_definitions
   register: sensu_available_client_definitions
   changed_when: false
   changed_when: false
   become: false
   become: false
+  run_once: true
 
 
 - name: Deploy client definitions
 - name: Deploy client definitions
   copy:
   copy:
@@ -109,7 +113,40 @@
     group: "{{ sensu_group_name }}"
     group: "{{ sensu_group_name }}"
   when:
   when:
     - sensu_available_client_definitions is defined
     - sensu_available_client_definitions is defined
-    - sensu_available_checks is not skipped
+    - sensu_available_client_definitions is not skipped
     - item in sensu_available_client_definitions.stdout_lines
     - item in sensu_available_client_definitions.stdout_lines
   loop: "{{ group_names|flatten }}"
   loop: "{{ group_names|flatten }}"
   notify: restart sensu-client service
   notify: restart sensu-client service
+
+- name: Register available client templates
+  command: "ls {{ static_data_store }}/sensu/client_templates"
+  delegate_to: localhost
+  register: sensu_available_client_templates
+  changed_when: false
+  become: false
+  run_once: true
+
+- name: Deploy client template folders
+  file:
+    path: '{{ sensu_config_path }}/conf.d/{{ item | basename }}'
+    state: directory
+    owner: "{{ sensu_user_name }}"
+    group: "{{ sensu_group_name }}"
+  when:
+    - sensu_available_client_templates is defined
+    - sensu_available_client_templates is not skipped
+    - item in sensu_available_client_templates.stdout_lines
+  loop: "{{ group_names|flatten }}"
+  notify: restart sensu-client service
+
+- name: Deploy client templates
+  template:
+    src: "{{ static_data_store }}/sensu/client_templates/{{ item.path | dirname }}/{{ item.path | basename }}"
+    dest: "{{ sensu_config_path }}/conf.d/{{ item.path | dirname }}/{{ item.path | basename | regex_replace('.j2', '') }}"
+    owner: "{{ sensu_user_name }}"
+    group: "{{ sensu_group_name }}"
+  with_filetree: "{{ static_data_store }}/sensu/client_templates"
+  when:
+    - item.state == 'file'
+    - item.path | dirname in group_names
+  notify: restart sensu-client service