Implemented new jenkins_job states.
Added forgotten node enforcement statement.
Fixed PEP8 errors.
diff --git a/_states/jenkins_job.py b/_states/jenkins_job.py
new file mode 100644
index 0000000..e28bed8
--- /dev/null
+++ b/_states/jenkins_job.py
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+
+# Import Python libs
+from __future__ import absolute_import
+import difflib
+import logging
+
+# Import Salt libs
+import salt.ext.six as six
+import salt.utils
+
+# Import XML parser
+import xml.etree.ElementTree as ET
+
+log = logging.getLogger(__name__)
+
+
+def _elements_equal(e1, e2):
+    if e1.tag != e2.tag:
+        return False
+    if e1.text != e2.text:
+        return False
+    if e1.tail != e2.tail:
+        return False
+    if e1.attrib != e2.attrib:
+        return False
+    if len(e1) != len(e2):
+        return False
+    return all(_elements_equal(c1, c2) for c1, c2 in zip(e1, e2))
+
+
+def present(name,
+            config=None,
+            **kwargs):
+    '''
+    Ensure the job is present in the Jenkins
+    configured jobs
+    name
+        The unique name for the Jenkins job
+    config
+        The Salt URL for the file to use for
+        configuring the job.
+    '''
+
+    ret = {'name': name,
+           'result': True,
+           'changes': {},
+           'comment': ['Job {0} is up to date.'.format(name)]}
+
+    _job_exists = __salt__['jenkins.job_exists'](name)
+
+    if _job_exists:
+        _current_job_config = __salt__['jenkins.get_job_config'](name)
+        buf = six.moves.StringIO(_current_job_config)
+        oldXML = ET.fromstring(buf.read())
+
+        cached_source_path = __salt__['cp.cache_file'](config, __env__)
+        with salt.utils.fopen(cached_source_path) as _fp:
+            newXML = ET.fromstring(_fp.read())
+        if not _elements_equal(oldXML, newXML):
+            diff = difflib.unified_diff(
+                oldXML.tostring(), newXML.tostring(), lineterm='')
+            __salt__['jenkins.update_job'](name, config, __env__)
+            ret['changes'] = ''.join(diff)
+            ret['comment'].append('Job {0} updated.'.format(name))
+
+    else:
+        cached_source_path = __salt__['cp.cache_file'](config, __env__)
+        with salt.utils.fopen(cached_source_path) as _fp:
+            new_config_xml = _fp.read()
+
+        __salt__['jenkins.create_job'](name, config, __env__)
+
+        buf = six.moves.StringIO(new_config_xml)
+        _current_job_config = buf.readlines()
+
+        diff = difflib.unified_diff('', buf, lineterm='')
+        ret['changes'] = ''.join(diff)
+        ret['comment'].append('Job {0} added.'.format(name))
+
+    ret['comment'] = '\n'.join(ret['comment'])
+    return ret
+
+
+def absent(name,
+           **kwargs):
+    '''
+    Ensure the job is present in the Jenkins
+    configured jobs
+
+    name
+        The name of the Jenkins job to remove.
+
+    '''
+
+    ret = {'name': name,
+           'result': True,
+           'changes': {},
+           'comment': []}
+
+    _job_exists = __salt__['jenkins.job_exists'](name)
+
+    if _job_exists:
+        __salt__['jenkins.delete_job'](name)
+        ret['comment'] = 'Job {0} deleted.'.format(name)
+    else:
+        ret['comment'] = 'Job {0} already absent.'.format(name)
+    return ret