Merge pull request #3 from tcpcloud/client_jobs

Jenkins client for job enforcement
diff --git a/README.rst b/README.rst
index 237812c..44fe5ba 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,18 @@
         - name: rebuild
         - name: test-stability
 
-Jenkins slave
+Jenkins with experimental plugin source support
+
+.. code-block:: yaml
+
+    jenkins:
+      master:
+        enabled: true
+        update_site_url: 'http://updates.jenkins-ci.org/experimental/update-center.json'
+
+
+Agent (former slave)
+--------------------
 
 .. code-block:: yaml
 
@@ -111,6 +131,148 @@
             -----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
+
+Inline Groovy script samples
+
+.. code-block:: yaml
+
+    jenkins:
+      client:
+        job:
+          test_workflow_jenkins_simple:
+            type: workflow
+            display_name: Test jenkins simple workflow
+            script:
+              content: |
+                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:
+              content: |
+                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')
+                   ]
+                }
+
+
+GIT controlled groovy script samples
+
+.. code-block:: yaml
+
+    jenkins:
+      client:
+        source:
+          base:
+           engine: git
+            address: repo_url
+            branch: branch
+          domain:
+           engine: git
+            address: domain_url
+            branch: branch
+        job:
+          test_workflow_jenkins_simple:
+            type: workflow
+            display_name: Test jenkins simple workflow
+            param:
+              bool_param:
+                type: boolean
+                description: true/false
+                default: true
+            script:
+              repository: base
+              file: workflows/test_workflow_jenkins_simple.groovy
+          test_workflow_jenkins_input:
+            type: workflow
+            display_name: Test jenkins workflow inputs
+            script:
+              repository: domain
+              file: workflows/test_workflow_jenkins_input.groovy
+          test_workflow_jenkins_input_jenkinsfile:
+            type: workflow
+            display_name: Test jenkins workflow inputs (jenknisfile)
+            script:
+              repository: domain
+              file: workflows/test_workflow_jenkins_input/Jenkinsfile
+
+GIT controlled groovy script with shared libraries
+
+.. code-block:: yaml
+
+    jenkins:
+      client:
+        source:
+          base:
+           engine: git
+            address: repo_url
+            branch: branch
+          domain:
+           engine: git
+            address: domain_url
+            branch: branch
+        job:
+          test_workflow_jenkins_simple:
+            type: workflow
+            display_name: Test jenkins simple workflow
+            param:
+              bool_param:
+                type: boolean
+                description: true/false
+                default: true
+            script:
+              repository: base
+              file: workflows/test_workflow_jenkins_simple.groovy
+            libs:
+            - repository: base
+              file: macros/cookiecutter.groovy
+            - repository: base
+              file: macros/git.groovy
+
 Usage
 =====
 
diff --git a/jenkins/client.sls b/jenkins/client.sls
new file mode 100644
index 0000000..0228894
--- /dev/null
+++ b/jenkins/client.sls
@@ -0,0 +1,68 @@
+{% from "jenkins/map.jinja" import client with context %}
+{%- if client.enabled %}
+
+jenkins_client_install:
+  pkg.installed:
+  - names: {{ client.pkgs }}
+
+jenkins_client_dirs:
+  file.directory:
+  - names:
+    - /srv/jenkins
+    - {{ client.dir.salt_root }}/_jenkins/cache
+  - makedirs: true
+
+/etc/salt/minion.d/_jenkins.conf:
+  file.managed:
+  - source: salt://jenkins/files/_jenkins.conf
+  - template: jinja
+
+{%- for source_name, source in client.source.iteritems() %}
+
+{%- if source.engine == "git" %}
+
+jenkins_{{ source_name }}_source:
+  git.latest:
+  - name: {{ source.address }}
+  - target: {{ client.dir.jenkins_root }}/{{ source_name }}
+  - rev: {{ source.branch }}
+  - reload_pillar: True
+
+{%- elif client.source.engine == "local" %}
+
+jenkins_{{ source_name }}_dir:
+  file.managed:
+  - name: {{ client.dir.jenkins_root }}/{{ source_name }}
+  - mode: 700
+
+{%- endif %}
+
+{%- endfor %}
+
+{{ client.dir.salt_root }}/_jenkins/jobs:
+  file.symlink:
+    - target: {{ client.dir.jenkins_root }}
+
+{%- for job_name, job in client.job.iteritems() %}
+
+{{ client.dir.salt_root }}/_jenkins/cache/{{ 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/cache/{{ job_name }}.xml
+  - require:
+    - file: {{ client.dir.salt_root }}/_jenkins/cache/{{ job_name }}.xml
+
+{%- endfor %}
+
+{%- endif %}
diff --git a/jenkins/files/_jenkins.conf b/jenkins/files/_jenkins.conf
new file mode 100644
index 0000000..42cf6fd
--- /dev/null
+++ b/jenkins/files/_jenkins.conf
@@ -0,0 +1,7 @@
+{%- from "jenkins/map.jinja" import client with context %}
+jenkins:
+  url: "{{ client.master.get('proto', 'http') }}://{{ client.master.host }}:{{ client.master.port }}"
+  {%- if client.master.username is defined %}
+  user: {{ client.master.username }}
+  password: {{ client.master.password }}
+  {%- endif %}
\ No newline at end of file
diff --git a/jenkins/files/hudson.model.UpdateCenter.xml b/jenkins/files/hudson.model.UpdateCenter.xml
new file mode 100644
index 0000000..72a1189
--- /dev/null
+++ b/jenkins/files/hudson.model.UpdateCenter.xml
@@ -0,0 +1,8 @@
+{%- from "jenkins/map.jinja" import server with context %}
+<?xml version='1.0' encoding='UTF-8'?>
+<sites>
+  <site>
+    <id>default</id>
+    <url>{{ server.update_site_url }}</url>
+  </site>
+</sites>
\ 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..aab96e8
--- /dev/null
+++ b/jenkins/files/jobs/workflow.xml
@@ -0,0 +1,69 @@
+<?xml version='1.0' encoding='UTF-8'?>
+{%- from "jenkins/map.jinja" import client with context -%}
+{%- set job = salt['pillar.get']('jenkins:client:job:'+job_name) -%}
+{%- macro load_groovy_file() %}
+{%- set groovy_file='_jenkins/jobs/'+job.script.repository+'/'+job.script.file %}
+{%- include groovy_file %}
+{%- endmacro %}
+{%- macro load_groovy_lib(lib) %}
+{%- set groovy_file='_jenkins/jobs/'+lib.repository+'/'+lib.file %}
+{%- include groovy_file %}
+{%- endmacro %}
+<flow-definition plugin="workflow-job@2.5">
+  <description>
+    Salt generated project, do not edit. Changes will be overwritten.
+    {{ job.get('description', '')|e }}
+  </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 %}
+          {%- if param.default is defined %}
+          <defaultValue>{{ param.default|e }}</defaultValue>
+          {%- endif %}
+        </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>// libraries
+{%- if job.libs is defined %}
+{%- for lib in job.libs %}
+{{ load_groovy_lib(lib)|e }}
+{%- endfor %}
+{%- endif %}
+// workflow
+
+{%- if job.script.file is defined %}
+{{ load_groovy_file()|e }}
+{%- else %}
+{{ job.script.get('content', '')|e }}
+{%- endif %}
+    </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..27e57ba 100644
--- a/jenkins/map.jinja
+++ b/jenkins/map.jinja
@@ -4,11 +4,13 @@
         'pkgs': ['jenkins'],
         'service': 'jenkins',
         'config': '/etc/default/jenkins',
+        'update_site_url': 'http://updates.jenkins-ci.org/update-center.json'
     },
     'RedHat': {
         'pkgs': ['jenkins'],
         'service': 'jenkins',
         'config': '/etc/sysconfig/jenkins',
+        'update_site_url': 'http://updates.jenkins-ci.org/update-center.json'
     },
 }, merge=salt['pillar.get']('jenkins:master')) %}
 
@@ -57,3 +59,24 @@
         'config': '/etc/sysconfig/jenkins-slave',
     },
 }, merge=salt['pillar.get']('jenkins:slave')) %}
+
+{% set param = {'bool': 'Boolean', 'string': 'String', 'text': 'Text', 'choice': 'Choice'}  %}
+
+{% set client = salt['grains.filter_by']({
+    'Debian': {
+        'pkgs': ['python-jenkins'],
+        'param': param,
+        'dir': {
+            'salt_root': '/srv/salt/env/dev',
+            'jenkins_root': '/srv/jenkins/client',
+        }
+    },
+    'RedHat': {
+        'pkgs': ['python-jenkins'],
+        'param': param,
+        'dir': {
+            'salt_root': '/srv/salt/env/dev',
+            'jenkins_root': '/srv/jenkins/client',
+        }
+    },
+}, 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 93%
rename from jenkins/master/plugins.sls
rename to jenkins/master/plugin.sls
index 49fb6a0..6c618e9 100644
--- a/jenkins/master/plugins.sls
+++ b/jenkins/master/plugin.sls
@@ -12,8 +12,6 @@
     - wget http://localhost:{{ master.http.port }}/jnlpJars/jenkins-cli.jar
   - unless: "[ -f /root/jenkins-cli.jar ]"
   - cwd: /root
-  - require:
-    - service: jenkins_master_service
 
 {%- for plugin in master.plugins %}
 
diff --git a/jenkins/master/service.sls b/jenkins/master/service.sls
index 5145844..d01a001 100644
--- a/jenkins/master/service.sls
+++ b/jenkins/master/service.sls
@@ -27,7 +27,14 @@
   - template: jinja
   {%- endif %}
   - user: jenkins
-  - group: nogroup
+  - require:
+    - pkg: jenkins_packages
+
+/var/lib/jenkins/hudson.model.UpdateCenter.xml:
+  file.managed:
+  - source: salt://jenkins/files/hudson.model.UpdateCenter.xml
+  - template: jinja
+  - user: jenkins
   - require:
     - pkg: jenkins_packages
 
@@ -51,5 +58,6 @@
   - watch:
     - file: jenkins_{{ master.config }}
     - file: /var/lib/jenkins/config.xml
+    - file: /var/lib/jenkins/hudson.model.UpdateCenter.xml
 
 {%- endif %}
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'