Add state to manage AuditTrail Logger

PROD-31662

Change-Id: Id1abc4020d984699d28580179463ac31e1c27f64
diff --git a/_states/jenkins_audittrail.py b/_states/jenkins_audittrail.py
new file mode 100644
index 0000000..ced671e
--- /dev/null
+++ b/_states/jenkins_audittrail.py
@@ -0,0 +1,84 @@
+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_audittrail state module cannot be loaded: '
+            'jenkins_common not found')
+    return True
+
+def config (name, type="console", log="", limit=0, count=0, output="STD_OUT",
+            date_format="yyyy-MM-dd HH:mm:ss:SSS", log_prefix="", syslog_server_hostname="",
+            syslog_server_port=514, syslog_facility="SYSLOG",
+            app_name="jenkins", message_hostname="", message_format="RFC_3164"):
+    """
+    Jenkins AuditTrail logging config state method
+
+    :param type: logger type. one of: console (default), file, syslog
+    Console Logger params:
+    :param output: STD_OUT or STD_ERR
+    :param date_format
+    :param log_prefix
+    File Logger params:
+    :param log: path to log file
+    :param limit: log file size
+    :param count: log file count to keep
+    Syslog Logger params:
+    :param syslog_server_hostname
+    :param syslog_server_port
+    :param syslog_facility
+    :param app_name
+    :param message_hostname
+    :param message_format: RFC_3164 or RFC_5424
+
+    :returns: salt-specified state dict
+    """
+
+    template = __salt__['jenkins_common.load_template'](
+        'salt://jenkins/files/groovy/audittrail_config.template',
+        __env__)
+    return __salt__['jenkins_common.api_call'](name, template,
+                        ["CREATED", "EXISTS"],
+                        {
+                            'logger_type': type,
+                            'output': output,
+                            'date_format': date_format,
+                            'log_prefix': log_prefix if log_prefix else "",
+                            'log': log if log else "",
+                            'limit': limit,
+                            'count': count,
+                            'syslog_server_hostname': syslog_server_hostname if syslog_server_hostname else "",
+                            'syslog_server_port': syslog_server_port,
+                            'syslog_facility': syslog_facility,
+                            'app_name': app_name,
+                            'message_hostname': message_hostname if message_hostname else "",
+                            'message_format': message_format if message_format else "",
+                        },
+                        'AuditTrail logger configuration')
+
+def params (name, pattern="", log_build_cause=True):
+    """
+    Jenkins AuditTrail logging params state method
+
+    :param pattern
+    :param log_build_cause
+
+    :returns: salt-specified state dict
+    """
+
+    template = __salt__['jenkins_common.load_template'](
+        'salt://jenkins/files/groovy/audittrail_params.template',
+        __env__)
+    return __salt__['jenkins_common.api_call'](name, template,
+                                               ["CHANGED", "EXISTS"],
+                                               {
+                                                   'pattern': pattern if pattern else "",
+                                                   'log_build_cause': str(log_build_cause).lower(),
+                                               },
+                                               'AuditTrail logger params')
diff --git a/jenkins/client/audittrail.sls b/jenkins/client/audittrail.sls
new file mode 100644
index 0000000..18f9937
--- /dev/null
+++ b/jenkins/client/audittrail.sls
@@ -0,0 +1,27 @@
+{%- from "jenkins/map.jinja" import client with context %}
+{%- if client.audittrail is defined %}
+{%- if client.audittrail.pattern is defined %}
+configure_jenkins_auditrail:
+  jenkins_audittrail.params:
+    - pattern: {{ client.audittrail.pattern }}
+    - log_build_cause: {{ client.audittrail.get('log_build_cause', True) }}
+{%- endif %}
+
+{% for name, logger in client.audittrail.get('loggers', {}).iteritems() %}
+configure_jenkins_logger_{{ name }}:
+  jenkins_audittrail.config:
+    - type: {{ logger.get('type', 'console') }}
+    - output: {{ logger.get('output', 'STD_OUT') }}
+    - date_format: {{ logger.get('date_format', 'yyyy-MM-dd HH:mm:ss:SSS') }}
+    - log_prefix: {{ logger.get('log_prefix', '') }}
+    - log: {{ logger.get('log', '') }}
+    - limit: {{ logger.get('limit', 0) }}
+    - count: {{ logger.get('count', 0) }}
+    - syslog_server_hostname: {{ logger.get('syslog_server_hostname', '') }}
+    - syslog_server_port: {{ logger.get('syslog_server_port', 514) }}
+    - syslog_facility: {{ logger.get('syslog_facility', 'SYSLOG') }}
+    - app_name: {{ logger.get('app_name', 'jenkins') }}
+    - message_hostname: {{ logger.get('message_hostname', '') }}
+    - message_format: {{ logger.get('message_format', 'RFC_3164') }}
+{% endfor %}
+{%- endif %}
\ No newline at end of file
diff --git a/jenkins/client/init.sls b/jenkins/client/init.sls
index 85cacb3..99b2897 100644
--- a/jenkins/client/init.sls
+++ b/jenkins/client/init.sls
@@ -74,6 +74,9 @@
 {%- if client.flowdurabilitylevel is defined %}
   - jenkins.client.flowdurability
 {%- endif %}
+{%- if client.audittrail is defined %}
+  - jenkins.client.audittrail
+{%- endif %}
 
 # execute job enforcements as last
 {%- if client.job is defined %}
diff --git a/jenkins/files/groovy/audittrail_config.template b/jenkins/files/groovy/audittrail_config.template
new file mode 100644
index 0000000..9eef4b4
--- /dev/null
+++ b/jenkins/files/groovy/audittrail_config.template
@@ -0,0 +1,66 @@
+#!groovy
+
+import jenkins.model.*
+import hudson.*
+import hudson.plugins.audit_trail.*;
+
+def loggerType = "${logger_type}"
+Jenkins j = Jenkins.getInstance();
+AuditTrailPlugin plugin = j.getPlugin(AuditTrailPlugin.class);
+def entry
+def result
+
+switch (loggerType) {
+  case 'file':
+    result = plugin.loggers.find{
+               it instanceof LogFileAuditLogger &&
+               it.getLog() == "${log}" &&
+               it.getLimit().toString() == "${limit}" &&
+               it.getCount().toString() == "${count}"
+             }
+    entry = new LogFileAuditLogger("${log}", ${limit}, ${count})
+    break
+  case 'syslog':
+    result = plugin.loggers.find{
+               it instanceof SyslogAuditLogger &&
+               it.getSyslogServerHostname() == "${syslog_server_hostname}" &&
+               it.getSyslogServerPort().toString() == "${syslog_server_port}" &&
+               it.getAppName() == "${app_name}" &&
+               it.getMessageHostname() == ("${message_hostname}" ?: null) &&
+               it.getFacility() == "${syslog_facility}" &&
+               it.getMessageFormat() == "${message_format}"
+             }
+    entry = new SyslogAuditLogger("${syslog_server_hostname}", ${syslog_server_port}, "${app_name}",
+                                  "${message_hostname}", "${syslog_facility}", "${message_format}")
+    break
+  default:
+    result = plugin.loggers.find{
+               it instanceof ConsoleAuditLogger &&
+               it.getOutput().toString() == "${output}" &&
+               it.getDateFormat() == "${date_format}" &&
+               it.getLogPrefix() == "${log_prefix}"
+             }
+    entry = new ConsoleAuditLogger(ConsoleAuditLogger.Output.${output}, "${date_format}", "${log_prefix}")
+    break
+}
+
+if (result) {
+  print("EXISTS")
+} else {
+  switch (loggerType) {
+    case 'file':
+      plugin.loggers.removeAll { it instanceof LogFileAuditLogger && it.getLog() == "${log}" }
+      break
+    case 'syslog':
+      plugin.loggers.removeAll { it instanceof SyslogAuditLogger &&
+            it.getSyslogServerHostname() == "${syslog_server_hostname}" }
+      break
+    default:
+      plugin.loggers.removeAll { it instanceof ConsoleAuditLogger && it.getOutput().toString() == "${output}" }
+      break
+  }
+  plugin.loggers.add(entry)
+  plugin.save()
+  plugin.start()
+  print("CREATED")
+}
diff --git a/jenkins/files/groovy/audittrail_params.template b/jenkins/files/groovy/audittrail_params.template
new file mode 100644
index 0000000..c1b5486
--- /dev/null
+++ b/jenkins/files/groovy/audittrail_params.template
@@ -0,0 +1,25 @@
+#!groovy
+
+import jenkins.model.*
+import hudson.*
+import hudson.plugins.audit_trail.AuditTrailPlugin;
+
+Jenkins j = Jenkins.getInstance();
+AuditTrailPlugin plugin = j.getPlugin(AuditTrailPlugin.class);
+def result = 'EXISTS'
+
+if (plugin.getLogBuildCause() != "${log_build_cause}".toBoolean()) {
+  result = 'CHANGED'
+  plugin.logBuildCause = "${log_build_cause}".toBoolean()
+}
+if (plugin.getPattern() != "${pattern}") {
+  result = 'CHANGED'
+  plugin.pattern = "${pattern}"
+}
+
+if (result == 'CHANGED') {
+  plugin.save()
+  plugin.start()
+}
+
+print(result)
diff --git a/jenkins/schemas/client.yaml b/jenkins/schemas/client.yaml
index 8373397..0e5355b 100644
--- a/jenkins/schemas/client.yaml
+++ b/jenkins/schemas/client.yaml
@@ -523,6 +523,50 @@
             type: string
           color:
             type: string
+  audittrail:
+    description: Jenkins configuration of Audit Trail plugin
+    type: object
+    additionalProperties: false
+    properties:
+      pattern:
+        type: string
+      log_build_cause:
+        type: boolean
+      loggers:
+        type: object
+        additionalProperties: false
+        patternProperties:
+          "^[A-Za-z0-9_\\-]*$":
+            type: object
+            additionalProperties: false
+            properties:
+              type:
+                type: string
+              output:
+                type: string
+                enum: ['STD_OUT', 'STD_ERR']
+              date_format:
+                type: string
+              log_prefix:
+                type: string
+              log:
+                type: string
+              limit:
+                type: integer
+              count:
+                type: integer
+              syslog_server_hostname:
+                type: string
+              syslog_server_port:
+                type: integer
+              syslog_facility:
+                type: string
+              app_name:
+                type: string
+              message_hostname:
+                type: string
+              message_format:
+                type: string
   job:
     description: Jenkins jobs configuration
     type: object
diff --git a/tests/pillar/client.sls b/tests/pillar/client.sls
index 21f5ed2..42c9d91 100644
--- a/tests/pillar/client.sls
+++ b/tests/pillar/client.sls
@@ -1,6 +1,22 @@
 jenkins:
   client:
     enabled: true
+    audittrail:
+      pattern: '.*'
+      loggers:
+        log_to_file:
+          type: file
+          log: "/var/log/jenkins_audit.log"
+          limit: 100
+          count: 10
+        log_to_syslog:
+          type: syslog
+          syslog_server_hostname: example.syslog.com
+          syslog_facility: LOCAL0
+          message_hostname: example.jenkins.com
+        log_to_console:
+           output: STD_OUT
+           date_format: "yyyy-MM-dd HH:mm:ss:SSS"
     flowdurabilitylevel: PERFORMANCE_OPTIMIZED
     master:
       host: jenkins.example.com