Add state to manage Jira sites

    Closes-PROD: https://mirantis.jira.com/browse/PROD-19349

Change-Id: Ic5a2a397b3a6e7d4159c9393317ce984dda320d0
diff --git a/README.rst b/README.rst
index d641952..d721873 100644
--- a/README.rst
+++ b/README.rst
@@ -766,6 +766,39 @@
           'My Category To Remove:
             enabled: false
 
+Jira sites management from client (requires
+`JIRA <https://plugins.jenkins.io/jira>`_ plugin)
+
+.. code-block:: yaml
+
+    # Remove all sites
+    jenkins:
+      client:
+        jira:
+          enabled: False
+
+.. code-block:: yaml
+
+    jenkins:
+      client:
+        jira:
+          sites:
+            'http://my.jira.site/':
+              link_url: 'http://alternative.link/'
+              http_auth: false
+              use_wiki_notation: false
+              record_scm: false
+              disable_changelog: false
+              issue_pattern: ''
+              any_build_result: false
+              user: 'username'
+              password: 'passwd'
+              conn_timeout: 10
+              visible_for_group: ''
+              visible_for_project: ''
+              timestamps: false
+              timestamp_format: ''
+
 Usage
 =====
 
diff --git a/_states/jenkins_jira.py b/_states/jenkins_jira.py
new file mode 100644
index 0000000..739b248
--- /dev/null
+++ b/_states/jenkins_jira.py
@@ -0,0 +1,71 @@
+import json
+import logging
+
+logger = logging.getLogger(__name__)
+
+def __virtual__():
+    '''
+    Only load if jenkins_common module exist.
+    '''
+    if 'jenkins_common.call_groovy_script' not in __salt__:
+        return (
+            False,
+            'The jenkins_jira state module cannot be loaded: '
+            'jenkins_common not found')
+    return True
+
+def present (name, sites, **kwargs):
+    """
+    Jenkins Jira instance state method
+
+    :param name: ID name
+    :param sites:Jira sites dict
+    :
+    :sites[name] params:
+    :param link_url: root URL of JIRA installation for "normal" access
+    :param http_auth: connect to JIRA using HTTP Basic Authentication
+    :param use_wiki_notation: enable if JIRA supports Wiki notation
+    :param record_scm: record scm changes in JIRAA
+    :param disable_changelog: do not create JIRA hyperlinks in the changeset
+    :param issue_pattern: custom pattern to search for JIRA issue ids
+    :param any_build_result: update issues on any build result
+    :param user: JIRA user name
+    :param password: JIRA user password
+    :param conn_timeout: connection timeout for JIRA REST API calls
+    :param visible_for_group: allow to read comments for JIRA group
+    :param visible_for_project: allow to read comments for JIRA project
+    :param timestamps: enable SCM change date and time entries
+    :param timestamp_format: timestamp format
+    :
+    :returns: salt-specified state dict
+    """
+
+    template = __salt__['jenkins_common.load_template'](
+        'salt://jenkins/files/groovy/jira.template',
+        __env__)
+    return __salt__['jenkins_common.api_call'](name, template,
+                        ["CREATED", "EXISTS"],
+                        {
+                            'sites': json.dumps(sites),
+                            'absent': False
+                        },
+                        'JIRA server')
+
+def absent(name):
+    """
+    Jenkins Jira instance absence state method
+
+    :param name: ID name
+    :returns: salt-specified state dict
+    """
+    template = __salt__['jenkins_common.load_template'](
+        'salt://jenkins/files/groovy/jira.template',
+        __env__)
+    return __salt__['jenkins_common.api_call'](name, template,
+                        ["REMOVED", "NOT PRESENT"],
+                        {
+                            'sites': '{}',
+                            'absent': True
+                        },
+                        'JIRA server')
+
diff --git a/jenkins/client/init.sls b/jenkins/client/init.sls
index 7b0726f..46fb104 100644
--- a/jenkins/client/init.sls
+++ b/jenkins/client/init.sls
@@ -47,6 +47,9 @@
 {%- if client.throttle_category is defined %}
   - jenkins.client.throttle_category
 {%- endif %}
+{%- if client.jira is defined %}
+  - jenkins.client.jira
+{%- endif %}
 
 # execute job enforcements as last
 {%- if client.job is defined %}
diff --git a/jenkins/client/jira.sls b/jenkins/client/jira.sls
new file mode 100644
index 0000000..39d7105
--- /dev/null
+++ b/jenkins/client/jira.sls
@@ -0,0 +1,29 @@
+{% from "jenkins/map.jinja" import client with context %}
+{% if client.jira.get('enabled', True) %}
+jenkins_jira_enable:
+  jenkins_jira.present:
+    - sites: {
+      {% for name, site in client.jira.get('sites',[]).iteritems() %}
+      '{{ name }}': {
+        link_url: '{{ site.get('link_url', name) }}',
+        http_auth: {{ site.get('http_auth', false) }},
+        use_wiki_notation: {{ site.get('use_wiki_notation', false) }},
+        record_scm: {{ site.get('record_scm', false) }},
+        disable_changelog: {{ site.get('disable_changelog', false) }},
+        issue_pattern: '{{ site.get('issue_pattern', '') }}',
+        any_build_result: {{ site.get('any_build_result', false) }},
+        user: '{{ site.get('user', '') }}',
+        password: '{{ site.get('password', '') }}',
+        conn_timeout: {{ site.get('conn_timeout', 10) }},
+        visible_for_group: '{{ site.get('visible_for_group', '') }}',
+        visible_for_project: '{{ site.get('visible_for_project', '') }}',
+        timestamps: {{ site.get('timestamps', false) }},
+        timestamp_format: '{{ site.get('timestamp_format', '') }}'
+        },
+      {% endfor %}
+      }
+{% else %}
+jenkins_jira_disable:
+  jenkins_jira.absent
+{% endif %}
+
diff --git a/jenkins/files/groovy/jira.template b/jenkins/files/groovy/jira.template
new file mode 100644
index 0000000..33d6340
--- /dev/null
+++ b/jenkins/files/groovy/jira.template
@@ -0,0 +1,147 @@
+#!groovy
+import jenkins.model.*
+import java.net.URL
+import hudson.plugins.jira.JiraProjectProperty
+import hudson.plugins.jira.JiraSite
+import net.sf.json.JSONObject;
+import org.kohsuke.stapler.StaplerRequest
+
+def sites = JSONObject.fromObject('${sites}')
+def doRemove = '${absent}'.toBoolean()
+
+def isNotApplicable = true
+def doCreate = false
+def isRemoved = false
+
+if (Jenkins.instance.pluginManager.activePlugins.find {
+        it.shortName == "jira"}) {
+    isNotApplicable = false
+
+    def descriptor = Jenkins.getInstance().getDescriptorByType(
+            JiraProjectProperty.DescriptorImpl.class)
+
+    def _sites = descriptor.getSites()
+
+    if (doRemove) {
+        if (_sites.size() > 0) {
+            isRemoved = true
+            def sitesCollection = []
+            def sitesParams = [
+                sites: sitesCollection
+            ]
+            def jiraSites = sitesCollection.collect { it -> it as JiraSite }
+            def req = [
+                    bindJSONToList: { clazz, data -> jiraSites }
+                ] as org.kohsuke.stapler.StaplerRequest
+            descriptor.configure(req, new JSONObject())
+        }
+    } else {
+        def _site
+
+        // Recreate list in order to remove non-defined sites
+        _sites.each{
+            if (sites[it.url.toString()] == null) {
+                doCreate = true
+            }
+        }
+
+        sites.each{ name, params ->
+            _site = _sites.find{
+                it.url.toString() == (name ?: null) &&
+                it.alternativeUrl.toString() == (params['link_url'] ?: null) &&
+                it.userName == (params['user'] ?: null) &&
+                it.password.getPlainText() == params['password'] &&
+                it.supportsWikiStyleComment == params['use_wiki_notation'] &&
+                it.recordScmChanges == params['record_scm'] &&
+                it.userPattern.toString() == (params['issue_pattern'] ?: "null") &&
+                it.updateJiraIssueForAllStatus == params['any_build_result'] &&
+                it.groupVisibility == (params['visible_for_group'] ?: null) &&
+                it.roleVisibility == (params['visible_for_project'] ?: null) &&
+                it.useHTTPAuth == params['http_auth']
+            }
+            if (!_site) {
+              doCreate = true
+            }
+        }
+
+        if (doCreate) {
+            def sitesCollection = []
+            def siteEntry = []
+            sites.each{ name, params ->
+               siteEntry = [
+                    new URL(name),
+                    new URL(params['link_url']),
+                    params['user'],
+                    params['password'],
+                    params['use_wiki_notation'],
+                    params['record_scm'],
+                    params['issue_pattern'],
+                    params['any_build_result'],
+                    params['visible_for_group'],
+                    params['visible_for_project'],
+                    params['http_auth']
+               ]
+               sitesCollection.add(siteEntry)
+            }
+            def sitesParams = [
+                sites: sitesCollection
+            ]
+            def jiraSites = sitesCollection.collect { it -> it as JiraSite }
+            def req = [
+                    bindJSONToList: { clazz, data -> jiraSites }
+                ] as org.kohsuke.stapler.StaplerRequest
+            descriptor.configure(req, new JSONObject())
+        }
+
+        // update sites list
+        _sites = descriptor.getSites()
+
+        sites.each{ name, params ->
+            _site = _sites.find{
+                it.url.toString() == (name ?: null) &&
+                it.alternativeUrl.toString() == (params['link_url'] ?: null) &&
+                it.userName == (params['user'] ?: null) &&
+                it.password.getPlainText() == params['password'] &&
+                it.supportsWikiStyleComment == params['use_wiki_notation'] &&
+                it.recordScmChanges == params['record_scm'] &&
+                it.userPattern.toString() == (params['issue_pattern'] ?: "null") &&
+                it.updateJiraIssueForAllStatus == params['any_build_result'] &&
+                it.groupVisibility == (params['visible_for_group'] ?: null) &&
+                it.roleVisibility == (params['visible_for_project'] ?: null) &&
+                it.useHTTPAuth == params['http_auth']
+            }
+
+            if (_site.disableChangelogAnnotations != params['disable_changelog']) {
+                _site.setDisableChangelogAnnotations(params['disable_changelog'])
+                doCreate = true
+            }
+
+            if (_site.timeout != params['conn_timeout']) {
+                _site.timeout = params['conn_timeout']
+                doCreate = true
+            }
+
+            if (_site.appendChangeTimestamp != params['timestamps']) {
+                _site.appendChangeTimestamp = params['timestamps']
+                doCreate = true
+            }
+
+            if (_site.dateTimePattern != params['timestamp_format']) {
+                _site.dateTimePattern = params['timestamp_format']
+                doCreate = true
+            }
+        }
+    }
+}
+
+if (isNotApplicable) {
+   print("NOT APPLICABLE")
+} else if (doCreate) {
+   print("CREATED")
+} else if (isRemoved) {
+   print("REMOVED")
+} else if (doRemove) {
+   print("NOT PRESENT")
+} else {
+   print("EXISTS")
+}