Jenkins client for job enforcement
diff --git a/README.rst b/README.rst
index 237812c..b4bc7e6 100644
--- a/README.rst
+++ b/README.rst
@@ -13,17 +13,23 @@
 ``jenkins.master``
 ------------------
 
-Setup jenkins master
+Setup jenkins master.
 
 ``jenkins.slave``
 -----------------
 
-Setup jenkins slave
+Setup jenkins slave.
 
 ``jenkins.job_builder``
 -----------------------
 
-Setup jenkins job builder
+Setup jenkins job builder.
+
+``jenkins.client``
+------------------
+
+Setup jenkins client, works with Salt 2016.3+, supports pipeline workflow projects only now.
+
 
 Available metadata
 ==================
@@ -42,10 +48,13 @@
 
 Setup Jenkins slave
 
-Example pillars
-===============
+Sample pillars
+==============
 
 Jenkins master
+--------------
+
+Simple master with reverse proxy
 
 .. code-block:: yaml
 
@@ -89,7 +98,8 @@
         - name: rebuild
         - name: test-stability
 
-Jenkins slave
+Agent (former slave)
+--------------------
 
 .. code-block:: yaml
 
@@ -110,6 +120,72 @@
           private_key: |
             -----BEGIN PGP PRIVATE KEY BLOCK-----
             ...
+Client
+------
+
+Simple client with workflow job definition
+
+.. code-block:: yaml
+
+    jenkins:
+      client:
+        master:
+          host: jenkins.example.com
+          port: 80
+          protocol: http
+        job:
+          jobname:
+            type: workflow
+            param:
+              bool_param:
+                type: boolean
+                description: true/false
+                default: true
+              string_param:
+                type: string
+                description: 1 liner
+                default: default_string
+              text_param:
+                type: text
+                description: multi-liner
+                default: default_text
+
+Groovy script samples
+
+.. code-block:: yaml
+
+    jenkins:
+      client:
+        job:
+          test_workflow_jenkins_simple:
+            type: workflow
+            display_name: Test jenkins simple workflow
+            script: |
+              node {
+                 stage 'Stage 1'
+                 echo 'Hello World 1'
+                 stage 'Stage 2'
+                 echo 'Hello World 2'
+              }
+          test_workflow_jenkins_input:
+            type: workflow
+            display_name: Test jenkins workflow inputs
+            script: |
+              node {
+                 stage 'Enter string'
+                 input message: 'Enter job parameters', ok: 'OK', parameters: [
+                   string(defaultValue: 'default', description: 'Enter a string.', name: 'string'),
+                 ]
+                 stage 'Enter boolean'
+                 input message: 'Enter job parameters', ok: 'OK', parameters: [
+                   booleanParam(defaultValue: false, description: 'Select boolean.', name: 'Bool'),
+                 ]
+                 stage 'Enter text'
+                 input message: 'Enter job parameters', ok: 'OK', parameters: [
+                   text(defaultValue: '', description: 'Enter multiline', name: 'Multiline')
+                 ]
+              }
+
 
 Usage
 =====
diff --git a/jenkins/client.sls b/jenkins/client.sls
new file mode 100644
index 0000000..8bb0010
--- /dev/null
+++ b/jenkins/client.sls
@@ -0,0 +1,40 @@
+{% from "jenkins/map.jinja" import client with context %}
+{%- if client.enabled %}
+
+jenkins_client_install:
+  pkg.installed:
+  - names: {{ client.pkgs }}
+
+jenkins_client_dirs:
+  file.directory:
+  - name: /srv/salt/env/dev/_jenkins_jobs
+  - makedirs: true
+
+/etc/salt/minion.d/_jenkins.conf:
+  file.managed:
+  - source: salt://jenkins/files/_jenkins.conf
+  - template: jinja
+
+{%- for job_name, job in client.job.iteritems() %}
+
+/srv/salt/env/dev/_jenkins_jobs/{{ job_name }}.xml:
+  file.managed:
+  - source: salt://jenkins/files/jobs/{{ job.type }}.xml
+  - mode: 400
+  - template: jinja
+  - defaults:
+      job_name: {{ job_name }}
+  - require:
+    - file: jenkins_client_dirs
+    - file: /etc/salt/minion.d/_jenkins.conf
+
+jenkins_job_{{ job_name }}_ensure:
+  jenkins.present:
+  - name: {{ job_name }}
+  - config: salt://_jenkins_jobs/{{ job_name }}.xml
+  - require:
+    - file: /srv/salt/env/dev/_jenkins_jobs/{{ job_name }}.xml
+
+{%- endfor %}
+
+{%- endif %}
diff --git a/jenkins/files/_jenkins.conf b/jenkins/files/_jenkins.conf
new file mode 100644
index 0000000..4d716a1
--- /dev/null
+++ b/jenkins/files/_jenkins.conf
@@ -0,0 +1,5 @@
+{%- from "jenkins/map.jinja" import client with context %}
+jenkins:
+  url: "{{ client.master.get('proto', 'http') }}://{{ client.master.host }}:{{ client.master.port }}"
+  user: {{ client.master.username }}
+  password: {{ client.master.password }}
\ No newline at end of file
diff --git a/jenkins/files/jobs/workflow.xml b/jenkins/files/jobs/workflow.xml
new file mode 100644
index 0000000..9b10270
--- /dev/null
+++ b/jenkins/files/jobs/workflow.xml
@@ -0,0 +1,43 @@
+{%- from "jenkins/map.jinja" import client with context %}
+{%- set job = salt['pillar.get']('jenkins:client:job:'+job_name) -%}
+<?xml version='1.0' encoding='UTF-8'?>
+<flow-definition plugin="workflow-job@2.5">
+  <description>{{ job.get('description', 'Salt generated, do not edit.') }}</description>
+  {%- if job.display_name is defined %}
+  <displayName>{{ job.display_name }}</displayName>
+  {%- endif %}
+  <keepDependencies>false</keepDependencies>
+  <properties>
+    <org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
+      <triggers/>
+    </org.jenkinsci.plugins.workflow.job.properties.PipelineTriggersJobProperty>
+    {%- if job.param is defined %}
+    <hudson.model.ParametersDefinitionProperty>
+      <parameterDefinitions>
+        {%- for param_name, param in job.param.iteritems() %}
+        <hudson.model.{{ client.param[param.type] }}ParameterDefinition>
+          <name>{{ param_name }}</name>
+          <description>{{ param.get('description', '')|e }}</description>
+          {%- if param.type == "choice" %}
+          <choices class="java.util.Arrays$ArrayList">
+            <a class="string-array">
+              {%- for choice in param.choices %}
+              <string>{{ choice|e }}</string>
+              {%- endfor %}
+            </a>
+          </choices>
+          {%- endif %}
+          <defaultValue>{{ param.default|e }}</defaultValue>
+        </hudson.model.{{ client.param[param.type] }}ParameterDefinition>
+        {%- endfor %}
+      </parameterDefinitions>
+    </hudson.model.ParametersDefinitionProperty>
+    {%- endif %}
+  </properties>
+  <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.12">
+    <script>{{ job.script|e }}</script>
+    <sandbox>true</sandbox>
+  </definition>
+  <triggers>
+  </triggers>
+</flow-definition>
diff --git a/jenkins/init.sls b/jenkins/init.sls
index 985416e..db523b2 100644
--- a/jenkins/init.sls
+++ b/jenkins/init.sls
@@ -1,11 +1,14 @@
 
 include:
-{% if pillar.jenkins.master is defined %}
+{%- if pillar.jenkins.master is defined %}
 - jenkins.master
-{% endif %}
-{% if pillar.jenkins.job_builder is defined %}
-- jenkins.job_builder
-{% endif %}
-{% if pillar.jenkins.slave is defined %}
+{%- endif %}
+{%- if pillar.jenkins.slave is defined %}
 - jenkins.slave
-{% endif %}
+{%- if pillar.jenkins.job_builder is defined %}
+- jenkins.job_builder
+{%- endif %}
+{%- if pillar.jenkins.client is defined %}
+- jenkins.client
+{%- endif %}
+{%- endif %}
diff --git a/jenkins/map.jinja b/jenkins/map.jinja
index bbcf637..7336d88 100644
--- a/jenkins/map.jinja
+++ b/jenkins/map.jinja
@@ -57,3 +57,16 @@
         'config': '/etc/sysconfig/jenkins-slave',
     },
 }, merge=salt['pillar.get']('jenkins:slave')) %}
+
+{% set param = {'bool': 'Boolean', 'string': 'String', 'text': 'Text'}  %}
+
+{% set client = salt['grains.filter_by']({
+    'Debian': {
+        'pkgs': ['python-jenkins'],
+        'param': param,
+    },
+    'RedHat': {
+        'pkgs': ['python-jenkins'],
+        'param': param,
+    },
+}, merge=salt['pillar.get']('jenkins:client')) %}
diff --git a/jenkins/master/init.sls b/jenkins/master/init.sls
index d67eab6..7ee07ba 100644
--- a/jenkins/master/init.sls
+++ b/jenkins/master/init.sls
@@ -2,8 +2,8 @@
 {%- if master.enabled %}
 include:
 - jenkins.master.service
-- jenkins.master.users
+- jenkins.master.user
 {%- if master.plugins is defined %}
-- jenkins.master.plugins
+- jenkins.master.plugin
 {%- endif %}
 {%- endif %}
diff --git a/jenkins/master/plugins.sls b/jenkins/master/plugin.sls
similarity index 100%
rename from jenkins/master/plugins.sls
rename to jenkins/master/plugin.sls
diff --git a/jenkins/master/users.sls b/jenkins/master/user.sls
similarity index 100%
rename from jenkins/master/users.sls
rename to jenkins/master/user.sls
diff --git a/metadata/service/vendor_repo/stable_debian.yml b/metadata/service/vendor_repo/stable_debian.yml
new file mode 100644
index 0000000..bd2b8e8
--- /dev/null
+++ b/metadata/service/vendor_repo/stable_debian.yml
@@ -0,0 +1,8 @@
+parameters:
+  linux:
+    system:
+      repo:
+        jenkins:
+          enabled: true
+          source: 'deb http://pkg.jenkins.io/debian-stable binary/'
+          key_url: 'http://pkg.jenkins.io/debian-stable/jenkins.io.key'