Add client role

The client role is responsible for provisioning the users, databases
and privileges. It is required when running InfluxDB in a container
because the deployment of InfluxDB and the provisioning phase are
decoupled. Non-containerized deployments are left unchanged with the
provisioning managed by the server state.

Change-Id: I7c9a05f2109f76aadf84953374c740a865d59106
diff --git a/.kitchen.yml b/.kitchen.yml
index 2e338c4..ce9948e 100644
--- a/.kitchen.yml
+++ b/.kitchen.yml
@@ -44,6 +44,11 @@
 
 suites:
 
+  - name: client
+    provisioner:
+      pillars-from-files:
+        influxdb.sls: tests/pillar/client.sls
+
   - name: cluster
     provisioner:
       pillars-from-files:
diff --git a/README.rst b/README.rst
index 60d3cb8..c79788f 100644
--- a/README.rst
+++ b/README.rst
@@ -230,6 +230,40 @@
             source: 'deb https://repos.influxdata.com/${linux:system:os} ${linux:system:dist} stable'
             key_url: 'https://repos.influxdata.com/influxdb.key'
 
+InfluxDB client for configuring databases, users and retention policies:
+
+.. code-block:: yaml
+
+    influxdb:
+      client:
+        enabled: true
+        server:
+          protocol: http
+          host: 127.0.0.1
+          port: 8086
+          user: admin
+          password: foobar
+        user:
+          user1:
+            enabled: true
+            admin: true
+            name: username1
+        database:
+          mydb1:
+            enabled: true
+            name: mydb1
+            retention_policy:
+            - name: rp_db1
+              duration: 30d
+              replication: 1
+              is_default: true
+        grant:
+          username1_mydb1:
+            enabled: true
+            user: username1
+            database: mydb1
+            privilege: all
+
 Read more
 =========
 
diff --git a/influxdb/client.sls b/influxdb/client.sls
new file mode 100644
index 0000000..f312531
--- /dev/null
+++ b/influxdb/client.sls
@@ -0,0 +1,83 @@
+{%- from "influxdb/map.jinja" import client with context %}
+
+{%- if client.get('enabled') %}
+
+{%- set curl_command = 'curl' %}
+{%- if grains.get('noservices') %}
+{%- set curl_command = 'true ' + curl_command %}
+{%- endif %}
+
+{%- set noauth_url = "{}://{}:{}/query".format(client.server.protocol, client.server.host, client.server.port) %}
+{%- set auth_url = "{}?u={}&p={}".format(noauth_url, client.server.user, client.server.password) %}
+
+{# Create the admin user (this is only required on the first run) #}
+{% set create_admin_query = "--data-urlencode \"q=CREATE USER {} WITH PASSWORD '{}' WITH ALL PRIVILEGES\"".format(client.server.user, client.server.password) %}
+influxdb_create_admin:
+  cmd.run:
+  - name: {{ curl_command }} -f -S -POST "{{ noauth_url }}" {{ create_admin_query }} || {{ curl_command }} -f -S -POST "{{ auth_url }}" {{ create_admin_query }}
+
+{# Create the regular users #}
+{%- for user_name, user in client.get('user', {}).iteritems() %}
+  {%- if user.get('enabled', False) %}
+      {%- if user.get('admin', False) %}
+        {% set create_user_query = "--data-urlencode \"q=CREATE USER {} WITH PASSWORD '{}' WITH ALL PRIVILEGES\"".format(user.name, user.password) %}
+      {%- else %}
+        {% set create_user_query = "--data-urlencode \"q=CREATE USER {} WITH PASSWORD '{}'\"".format(user.name, user.password) %}
+      {%- endif %}
+influxdb_create_user_{{user.name}}:
+  cmd.run:
+    - name: {{ curl_command }} -f -S -POST "{{ auth_url }}" {{ create_user_query }}
+    - require:
+      - cmd: influxdb_create_admin
+  {%- endif %}
+{%- endfor %}
+
+{# Create the databases #}
+{%- for db_name, db in client.get('database', {}).iteritems() %}
+  {%- if db.get('enabled', False) %}
+    {% set create_db_query = "--data-urlencode \"q=CREATE DATABASE {}\"".format(db.name) %}
+influxdb_create_db_{{ db.name }}:
+  cmd.run:
+    - name: {{ curl_command }} -f -S -POST "{{ auth_url }}" {{ create_db_query }}
+    - require:
+      - cmd: influxdb_create_admin
+
+    {% for rp in db.get('retention_policy', []) %}
+    {% set rp_name = rp.get('name', 'autogen') %}
+    {% if rp.get('is_default') %}
+      {% set is_default = 'DEFAULT' %}
+    {% else %}
+      {% set is_default = '' %}
+    {% endif %}
+    {% set duration = rp.get('duration', 'INF') %}
+    {% set replication = rp.get('replication', '1') %}
+    {% if rp.get('shard_duration') %}
+      {% set shard_duration = 'SHARD DURATION {}'.format(rp.shard_duration) %}
+    {% else %}
+      {% set shard_duration = '' %}
+    {% endif %}
+    {% set query_retention_policy = 'RETENTION POLICY {} ON {} DURATION {} REPLICATION {} {} {}'.format(
+        rp_name, db.name, duration, replication, shard_duration, is_default)
+    %}
+influxdb_retention_policy_{{db.name}}_{{ rp_name }}:
+  cmd.run:
+    - name: {{ curl_command }} -s -S -POST "{{ auth_url }}" --data-urlencode "q=CREATE {{ query_retention_policy }}"|grep -v "policy already exists" || {{ curl_command }} -s -S -POST "{{ auth_url }}" --data-urlencode "q=ALTER {{ query_retention_policy }}"
+    - require:
+      - cmd: influxdb_create_db_{{db.name}}
+    {%- endfor %}
+  {%- endif %}
+{%- endfor %}
+
+{%- for grant_name, grant in client.get('grant', {}).iteritems() %}
+  {%- if grant.get('enabled', False) %}
+    {% set query_grant_user_access = "--data-urlencode \"q=GRANT {} ON {} TO {}\"".format(grant.privilege, grant.database, grant.user) %}
+influxdb_grant_{{ grant_name }}:
+  cmd.run:
+    - name: {{ curl_command }} -f -S -POST "{{ auth_url }}" {{ query_grant_user_access }}
+    - require:
+      - cmd: influxdb_create_db_{{ grant.database }}
+      - cmd: influxdb_create_user_{{ grant.user }}
+  {%- endif %}
+{%- endfor %}
+
+{%- endif %}
diff --git a/influxdb/init.sls b/influxdb/init.sls
index 9d0eeb9..a878715 100644
--- a/influxdb/init.sls
+++ b/influxdb/init.sls
@@ -1,4 +1,9 @@
-{% if pillar.influxdb.server is defined %}
+{%- if pillar.influxdb is defined %}
 include:
+{%- if pillar.influxdb.server is defined %}
 - influxdb.server
-{% endif %}
+{%- endif %}
+{%- if pillar.influxdb.client is defined %}
+- influxdb.client
+{%- endif %}
+{%- endif %}
diff --git a/influxdb/map.jinja b/influxdb/map.jinja
index c9a0419..c059cf4 100644
--- a/influxdb/map.jinja
+++ b/influxdb/map.jinja
@@ -41,3 +41,8 @@
     'dropped_points_percentage': 5,
   },
 }, grain='os_family', merge=salt['pillar.get']('influxdb:monitoring')) %}
+
+{%- set client = salt['grains.filter_by']({
+    'default': {
+    },
+}, merge=salt['pillar.get']('influxdb:client')) %}
diff --git a/metadata/service/client.yml b/metadata/service/client.yml
new file mode 100644
index 0000000..d64d7a1
--- /dev/null
+++ b/metadata/service/client.yml
@@ -0,0 +1,6 @@
+applications:
+- influxdb
+parameters:
+  influxdb:
+    client:
+      enabled: true
diff --git a/tests/pillar/client.sls b/tests/pillar/client.sls
new file mode 100644
index 0000000..5d1f0d2
--- /dev/null
+++ b/tests/pillar/client.sls
@@ -0,0 +1,41 @@
+influxdb:
+  client:
+    enabled: true
+    server:
+      protocol: http
+      host: 127.0.0.1
+      port: 8086
+      user: admin
+      password: foobar
+    user:
+      user1:
+        enabled: true
+        admin: true
+        name: username1
+        password: secret
+    database:
+      mydb1:
+        enabled: true
+        name: mydb1
+        retention_policy:
+        - name: rp_db1
+          duration: 30d
+          replication: 1
+          is_default: true
+        - name: rp_db2
+          duration: 365d
+          replication: 1
+      mydb2:
+        enabled: true
+        name: mydb2
+    grant:
+      username1_mydb1:
+        enabled: true
+        user: username1
+        database: mydb1
+        privilege: all
+      username1_mydb2:
+        enabled: true
+        user: username1
+        database: mydb2
+        privilege: read