Rewritten jenkins plugin to non block

This commit is contained in:
James Booth
2014-06-09 22:20:26 +01:00
parent dc02fc333f
commit 6a7588df61

View File

@@ -6,37 +6,42 @@ The jenkinsapi module is required https://pypi.python.org/pypi/jenkinsapi
To install the module using pip: To install the module using pip:
sudo pip install jenkinsapi sudo pip install jenkinsapi
The plugin polls the jenkins server at 'jenkins_url' every 'poll_interval' seconds. The plugin polls the jenkins server at 'jenkins_url' every 'jenkins_poll_interval' seconds.
The results are checked by profanity every 'prof_cb_interval'.
New failures are reported in the jenkins window, and a desktop notification is sent. New failures are reported in the jenkins window, and a desktop notification is sent.
The 'remind_interval' specified the time between reminder notifications of broken builds The 'prof_remind_interval' specifies the time between reminder notifications of broken builds
Both intervals can be disabled with a value of 0, which means builds must be checked manually using
the supplied commands.
The following commands are available: The following commands are available:
/jenkins jobs - list all jobs /jenkins list - list all jobs
/jenkins show [job] - show details of a specific job
/jenkins build [job] - trigger a build for the job /jenkins build [job] - trigger a build for the job
/jenkins open [job] - open the job in the systems default browser /jenkins open [job] - open the job in the systems default browser
/jenkins remind on|off - Enable or disable reminder notifications
/jenkins notify on|off - Enable or disable build status change notifications
""" """
import prof import prof
import os import os
import threading
import time
import webbrowser import webbrowser
import urllib2
import jenkinsapi import jenkinsapi
from jenkinsapi.jenkins import Jenkins from jenkinsapi.jenkins import Jenkins
win_tag = "Jenkins"
poll_interval = 10
remind_interval = 60 * 15
enable_remind = True
jenkins_url = "http://localhost:8080" jenkins_url = "http://localhost:8080"
username = None username = None
password = None password = None
jenkins_poll_interval = 5
prof_cb_interval = 5
prof_remind_interval = 60 * 15
enable_notify = True
enable_remind = False
last_state = {} last_state = {}
STATE_SUCCESS = "SUCCESS" STATE_SUCCESS = "SUCCESS"
STATE_UNSTABLE = "UNSTABLE" STATE_UNSTABLE = "UNSTABLE"
@@ -44,7 +49,33 @@ STATE_FAILURE = "FAILURE"
STATE_QUEUED = "QUEUED" STATE_QUEUED = "QUEUED"
STATE_RUNNING = "RUNNING" STATE_RUNNING = "RUNNING"
STATE_NOBUILDS = "NOBUILDS" STATE_NOBUILDS = "NOBUILDS"
STATE_UNKNOWN = "UNKNOWN" STATE_UNKNOWN = "UNKNOWN"
win_tag = "Jenkins"
poll_fail = False
poll_fail_message = None
job_list = None
changes_list = None
def _safe_remove(jobname, state):
if jobname in last_state[state]:
last_state[state].remove(jobname)
def _set_state(jobname, state):
if not jobname in last_state[state]:
_safe_remove(jobname, STATE_SUCCESS)
_safe_remove(jobname, STATE_UNSTABLE)
_safe_remove(jobname, STATE_FAILURE)
_safe_remove(jobname, STATE_QUEUED)
_safe_remove(jobname, STATE_RUNNING)
_safe_remove(jobname, STATE_NOBUILDS)
_safe_remove(jobname, STATE_UNKNOWN)
last_state[state].append(jobname)
return True
else:
return False
def _show_help(): def _show_help():
prof.win_show(win_tag, "Commands:") prof.win_show(win_tag, "Commands:")
@@ -67,189 +98,228 @@ def _open_job_url(url):
os.dup2(savout, 1) os.dup2(savout, 1)
os.dup2(saverr, 2) os.dup2(saverr, 2)
def _safe_remove(jobname, state): class JobList():
if jobname in last_state[state]: def __init__(self):
last_state[state].remove(jobname) self.jobs = []
def _set_state(jobname, state): def add_job(self, name, build_number, state):
if not jobname in last_state[state]: self.jobs.append((name, build_number, state))
_safe_remove(jobname, STATE_SUCCESS)
_safe_remove(jobname, STATE_UNSTABLE) def get_jobs(self):
_safe_remove(jobname, STATE_FAILURE) return self.jobs
_safe_remove(jobname, STATE_QUEUED)
_safe_remove(jobname, STATE_RUNNING) def contains_job(self, jobname):
_safe_remove(jobname, STATE_NOBUILDS) for name, build_number, state in self.jobs:
_safe_remove(jobname, STATE_UNKNOWN) if name == jobname:
last_state[state].append(jobname) return True
return True
else:
return False return False
def _list_jobs(): def num_failing(self):
try: count = 0
j = Jenkins(jenkins_url, username, password) for name, build_number, state in self.jobs:
except Exception, e: if state == "FAILURE":
prof.log_warning("Could not connect to jenkins: " + str(e)) count = count + 1
prof.win_show(win_tag, "Could not connect to Jenkins.")
prof.win_show(win_tag, "") return count
else:
if len(j.keys()) == 0: def num_unstable(self):
prof.win_show(win_tag, "No jobs available") count = 0
prof.win_show(win_tag, "") for name, build_number, state in self.jobs:
if state == "UNSTABLE":
count = count + 1
return count
class JobUpdates():
def __init__(self):
self.failing = []
self.passing = []
self.unstable = []
self.queued = []
self.running = []
def add_failing(self, name, build_number):
self.failing.append((name, build_number))
def get_failing(self):
return self.failing
def add_passing(self, name, build_number):
self.passing.append((name, build_number))
def get_passing(self):
return self.passing
def add_unstable(self, name, build_number):
self.unstable.append((name, build_number))
def get_unstable(self):
return self.unstable
def add_queued(self, name):
self.queued.append(name)
def get_queued(self):
return self.queued
def add_running(self, name):
self.running.append(name)
def get_running(self):
return self.running
def _jenkins_poll():
global poll_fail
global poll_fail_message
global job_list
global changes_list
while True:
time.sleep(jenkins_poll_interval)
try:
j = Jenkins(jenkins_url, username, password)
except Exception, e:
poll_fail = True
poll_fail_message = str(e)
else: else:
prof.win_show(win_tag, "Jobs:") poll_fail = False
poll_fail_message = None
new_job_list = JobList()
new_changes_list = JobUpdates()
for name, job in j.get_jobs(): for name, job in j.get_jobs():
if job.is_queued(): if not job.is_queued_or_running():
_set_state(name, STATE_QUEUED)
prof.win_show_cyan(win_tag, " " + name + ", QUEUED")
elif job.is_running():
_set_state(name, STATE_RUNNING)
prof.win_show_cyan(win_tag, " " + name + ", RUNNING")
else:
build = job.get_last_build_or_none() build = job.get_last_build_or_none()
if build: if build:
if build.get_status() == "FAILURE": if build.get_status() == "FAILURE":
_set_state(name, STATE_FAILURE) new_job_list.add_job(name, build.get_number(), "FAILURE")
prof.win_show_red(win_tag, " " + name + " #" + str(build.get_number()) + ", " + build.get_status()) changed = _set_state(name, STATE_FAILURE)
if changed:
new_changes_list.add_failing(name, build.get_number())
elif build.get_status() == "SUCCESS": elif build.get_status() == "SUCCESS":
_set_state(name, STATE_SUCCESS) new_job_list.add_job(name, build.get_number(), "SUCCESS")
prof.win_show_green(win_tag, " " + name + " #" + str(build.get_number()) + ", " + build.get_status()) changed = _set_state(name, STATE_SUCCESS)
if changed:
new_changes_list.add_passing(name, build.get_number())
elif build.get_status() == "UNSTABLE": elif build.get_status() == "UNSTABLE":
_set_state(name, STATE_UNSTABLE) new_job_list.add_job(name, build.get_number(), "UNSTABLE")
prof.win_show_yellow(win_tag, " " + name + " #" + str(build.get_number()) + ", " + build.get_status()) changed = _set_state(name, STATE_UNSTABLE)
else: if changed:
_set_state(name, STATE_UNKNOWN) new_changes_list.add_unstable(name, build.get_number())
prof.win_show_cyan(win_tag, " " + name + " #" + str(build.get_number()) + ", " + build.get_status())
else: else:
_set_state(name, STATE_NOBUILDS) new_job_list.add_job(name, None, "NOBUILDS")
prof.win_show(win_tag, " " + name + ", no builds")
prof.win_show(win_tag, "")
def _show_job(jobname):
try:
j = Jenkins(jenkins_url, username, password)
except Exception, e:
prof.log_warning("Could not connect to jenkins: " + str(e))
prof.win_show(win_tag, "Could not connect to Jenkins.")
prof.win_show(win_tag, "")
else:
if j.has_job(jobname):
job = j.get_job(jobname)
prof.win_show(win_tag, jobname + " details:");
desc = job.get_description()
if desc:
prof.win_show(win_tag, " Description : " + desc)
if job.is_queued():
_set_state(jobname, STATE_QUEUED)
prof.win_show_cyan(win_tag, " Status : Queued")
elif job.is_running():
_set_state(jobname, STATE_RUNNING)
prof.win_show_cyan(win_tag, " Status : Running")
else:
build = job.get_last_build_or_none()
if build:
prof.win_show(win_tag, " Last build : #" + str(build.get_number()))
if build.get_status() == "FAILURE":
_set_state(jobname, STATE_FAILURE)
prof.win_show_red(win_tag, " Status : " + build.get_status())
elif build.get_status() == "SUCCESS":
_set_state(jobname, STATE_SUCCESS)
prof.win_show_green(win_tag, " Status : " + build.get_status())
elif build.get_status() == "UNSTABLE":
_set_state(jobname, STATE_UNSTABLE)
prof.win_show_yellow(win_tag, " Status : " + build.get_status())
else:
_set_state(jobname, STATE_UNKNOWN)
prof.win_show_cyan(win_tag, " Status : " + build.get_status())
else: else:
_set_state(jobname, STATE_NOBUILDS) if job.is_queued():
prof.win_show(win_tag, " Last build : No builds") new_job_list.add_job(name, None, "QUEUED")
changed = _set_state(name, STATE_QUEUED)
if changed:
new_changes_list.add_queued(name)
elif job.is_running():
new_job_list.add_job(name, None, "RUNNING")
changed = _set_state(name, STATE_RUNNING)
if changed:
new_changes_list.add_running(name)
job_list = new_job_list
changes_list = new_changes_list
prof.win_show(win_tag, " Trigger : " + job.get_build_triggerurl()) def _prof_callback():
prof.win_show(win_tag, "") global changes_list
if poll_fail:
prof.win_show(win_tag, "Could not connect to jenkins, see the logs.")
if poll_fail_message:
prof.log_warning("Jenkins poll failed: " + str(poll_fail_message))
else: else:
prof.win_show(win_tag, "No such job " + jobname) prof.log_warning("Jenkins poll failed")
prof.win_show(win_tag, "") elif changes_list:
for name in changes_list.get_queued():
prof.win_show_cyan(win_tag, name + " QUEUED")
for name in changes_list.get_running():
prof.win_show_cyan(win_tag, name + " RUNNING")
for name, build_number in changes_list.get_passing():
prof.win_show_green(win_tag, name + " #" + str(build_number) + " SUCCESS")
if enable_notify:
prof.notify(name + " SUCCESS", 5000, "Jenkins")
for name, build_number in changes_list.get_unstable():
prof.win_show_yellow(win_tag, name + " #" + str(build_number) + " UNSTABLE")
if enable_notify:
prof.notify(name + " UNSTABLE", 5000, "Jenkins")
for name, build_number in changes_list.get_failing():
prof.win_show_red(win_tag, name + " #" + str(build_number) + " FAILURE")
if enable_notify:
prof.notify(name + " FAILURE", 5000, "Jenkins")
def _build_job(jobname): changes_list = None
try:
j = Jenkins(jenkins_url, username, password) def _cmd_jenkins(cmd=None, arg=None):
except Exception, e: global enable_remind
prof.log_warning("Could not connect to jenkins: " + str(e)) global enable_notify
prof.win_show(win_tag, "Could not connect to Jenkins.")
prof.win_show(win_tag, "") if cmd == "list":
else: if job_list and job_list.get_jobs():
if j.has_job(jobname): prof.win_show(win_tag, "Jobs:")
job = j.get_job(jobname) for name, build_number, state in job_list.get_jobs():
if state == "SUCCESS":
prof.win_show_green(win_tag, " " + name + " #" + str(build_number) + " SUCCESS")
elif state == "UNSTABLE":
prof.win_show_yellow(win_tag, " " + name + " #" + str(build_number) + " UNSTABLE")
elif state == "FAILURE":
prof.win_show_red(win_tag, " " + name + " #" + str(build_number) + " FAILURE")
elif state == "NOBUILDS":
prof.win_show(win_tag, " " + name + ", no builds")
else:
prof.win_show_cyan(win_tag, " " + name + " " + state)
else:
prof.win_show(win_tag, "No job data yet.")
elif cmd == "build":
if not arg:
prof.win_show(win_tag, "You must supply a job argument.")
elif job_list and job_list.contains_job(arg):
try: try:
job.invoke(invoke_pre_check_delay=1) urllib2.urlopen(jenkins_url + "/job/" + arg + "/build")
except Exception, e: except Exception, e:
prof.log_warning("Failed to trigger build: " + str(e)) prof.win_show(win_tag, "Failed to build " + arg + ", see the logs.")
prof.win_show(win_tag, "Failed to trigger build.") prof.log_warning("Failed to build " + arg + ": " + str(e))
prof.win_show(win_tag, "")
else:
prof.win_show(win_tag, "No such job " + jobname)
prof.win_show(win_tag, "")
def _open_job(jobname):
try:
j = Jenkins(jenkins_url, username, password)
except Exception, e:
prof.log_warning("Could not connect to jenkins: " + str(e))
prof.win_show(win_tag, "Could not connect to Jenkins.")
prof.win_show(win_tag, "")
else:
if not jobname:
prof.win_show(win_tag, "Usage: open [job]")
prof.win_show(win_tag, "")
else:
if j.has_job(jobname):
_open_job_url(jenkins_url + "/job/" + jobname)
else: else:
prof.win_show(win_tag, "No such job " + jobname) prof.win_show(win_tag, "Build request sent for " + arg)
prof.win_show(win_tag, "") else:
prof.win_show(win_tag, "No such job: " + arg)
def _process_job(name, job): elif cmd == "open":
if not job.is_queued_or_running(): if not arg:
build = job.get_last_build_or_none() prof.win_show(win_tag, "You must supply a job argument.")
if build: elif job_list and job_list.contains_job(arg):
if build.get_status() == "FAILURE": _open_job_url(jenkins_url + "/job/" + arg)
changed = _set_state(name, STATE_FAILURE) else:
if changed: prof.win_show(win_tag, "No such job: " + arg)
prof.win_show_red(win_tag, name + " #" + str(build.get_number()) + " FAILURE") elif cmd == "remind":
prof.win_show(win_tag, "") if not arg:
prof.notify(name + " FAILURE", 5000, "Jenkins") prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
elif build.get_status() == "SUCCESS": elif arg == "on":
changed = _set_state(name, STATE_SUCCESS) enable_remind = True
if changed: prof.win_show(win_tag, "Reminder notifications enabled.")
prof.win_show_green(win_tag, name + " #" + str(build.get_number()) + " SUCCESS") elif arg == "off":
prof.win_show(win_tag, "") enable_remind = False
prof.notify(name + " SUCCESS", 5000, "Jenkins") prof.win_show(win_tag, "Reminder notifications disabled.")
elif build.get_status() == "UNSTABLE": else:
changed = _set_state(name, STATE_UNSTABLE) prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
if changed: elif cmd == "notify":
prof.win_show_yellow(win_tag, name + " #" + str(build.get_number()) + " UNSTABLE") if not arg:
prof.win_show(win_tag, "") prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
prof.notify(name + " UNSTABLE", 5000, "Jenkins") elif arg == "on":
else: enable_notify = True
if job.is_queued(): prof.win_show(win_tag, "Build notifications enabled.")
changed = _set_state(name, STATE_QUEUED) elif arg == "off":
if changed: enable_notify = False
prof.win_show_cyan(win_tag, name + " QUEUED") prof.win_show(win_tag, "Build notifications disabled.")
prof.win_show(win_tag, "") else:
elif job.is_running(): prof.win_show(win_tag, "You must specify either 'on' or 'off'.")
changed = _set_state(name, STATE_RUNNING)
if changed:
prof.win_show_cyan(win_tag, name + " RUNNING")
prof.win_show(win_tag, "")
def _remind(): def _remind():
if enable_remind: if enable_remind and job_list:
notify_string = "" notify_string = ""
failing = len(last_state[STATE_FAILURE]) failing = job_list.num_failing()
unstable = len(last_state[STATE_UNSTABLE]) unstable = job_list.num_unstable()
if failing == 1: if failing == 1:
notify_string = notify_string + "1 failing build" notify_string = notify_string + "1 failing build"
@@ -267,74 +337,6 @@ def _remind():
if not notify_string == "": if not notify_string == "":
prof.notify(notify_string, 5000, "Jenkins") prof.notify(notify_string, 5000, "Jenkins")
def _poll_jobs():
try:
j = Jenkins(jenkins_url, username, password)
except Exception, e:
prof.log_warning("Could not connect to jenkins: " + str(e))
prof.win_show(win_tag, "Could not connect to Jenkins.")
prof.win_show(win_tag, "")
else:
for name, job in j.get_jobs():
_process_job(name, job)
def _cmd_jenkins(cmd=None, arg=None):
global enable_remind
if not prof.win_exists(win_tag):
prof.win_create(win_tag, _handle_input)
prof.win_focus(win_tag)
if not cmd:
_show_help()
else:
try:
j = Jenkins(jenkins_url, username, password)
except Exception, e:
prof.log_warning("Could not connect to jenkins: " + str(e))
prof.win_show(win_tag, "Could not connect to Jenkins.")
prof.win_show(win_tag, "")
else:
if cmd == "help":
_show_help()
elif cmd == "list":
_list_jobs()
elif cmd == "show":
if not arg:
prof.win_show(win_tag, "Usage: show [job]")
prof.win_show(win_tag, "")
else:
_show_job(arg)
elif cmd == "build":
if not arg:
prof.win_show(win_tag, "Usage: build [job]")
prof.win_show(win_tag, "")
else:
_build_job(arg)
elif cmd == "open":
if not arg:
prof.win_show(win_tag, "Usage: open [job]")
prof.win_show(win_tag, "")
else:
_open_job(arg)
elif cmd == "remind":
if not arg:
prof.win_show(win_tag, "Usage: remind on|off")
else:
if arg == "on":
enable_remind = True
prof.win_show(win_tag, "Reminders enabled.")
elif arg == "off":
enable_remind = False
prof.win_show(win_tag, "Reminders disabled.")
else:
prof.win_show(win_tag, "Usage: remind on|off")
prof.win_show(win_tag, "")
else:
prof.win_show(win_tag, "Usage: list|show|build|open|remind")
prof.win_show(win_tag, "")
def prof_init(version, status): def prof_init(version, status):
last_state[STATE_SUCCESS] = [] last_state[STATE_SUCCESS] = []
last_state[STATE_UNSTABLE] = [] last_state[STATE_UNSTABLE] = []
@@ -344,17 +346,18 @@ def prof_init(version, status):
last_state[STATE_NOBUILDS] = [] last_state[STATE_NOBUILDS] = []
last_state[STATE_UNKNOWN] = [] last_state[STATE_UNKNOWN] = []
if poll_interval > 0: jenkins_t = threading.Thread(target=_jenkins_poll)
prof.register_timed(_poll_jobs, poll_interval) jenkins_t.daemon = True;
if remind_interval > 0: jenkins_t.start()
prof.register_timed(_remind, remind_interval)
prof.register_command("/jenkins", 0, 2, "/jenkins list|show|build|open|remind", "Do jenkins stuff.", "Do jenkins stuff.", prof.register_timed(_prof_callback, prof_cb_interval)
prof.register_timed(_remind, prof_remind_interval)
prof.register_command("/jenkins", 0, 2, "/jenkins list|build|open|remind|notify", "Do jenkins stuff.", "Do jenkins stuff.",
_cmd_jenkins) _cmd_jenkins)
def _handle_input(win, line): def _handle_input(win, line):
_show_help() prof.win_show(win_tag, "Handled input.")
def prof_on_start(): def prof_on_start():
prof.win_create(win_tag, _handle_input) prof.win_create(win_tag, _handle_input)
_show_help() prof.win_show(win_tag, "Jenkins plugin started.")
_list_jobs()