From: Ramon van Alteren Date: Thu, 5 Jan 2012 14:42:19 +0000 (+0100) Subject: Renamed to jenkinsapi X-Git-Tag: v0.2.23~341 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=2ae5a0901b21d716b9bc2ea7e6f2129d10022946;p=tools%2Fpython-jenkinsapi.git Renamed to jenkinsapi Updated everything to new name Added readme with details and info of parent project Added TODO file Fixed all outstanding warnings --- diff --git a/README b/README index 0b62cb6..5fbd692 100644 --- a/README +++ b/README @@ -1,2 +1,22 @@ -Cloned copy of http://pyjenkinsci.googlecode.com/ -This file was modified by PyCharm 2.0.1 for binding GitHub repository +The original project is pyjenkinsci on googlecode: http://pyjenkinsci.googlecode.com/ + +A number of refactoring has been done: + * uniform usage of jenkins instead of mixed jenkins/hudson + * import fixup, all imports are relative to packagename now + * downloading artifacts is simplified + +New features: + * username/password auth support for jenkins instances with auth turned on + * Ability to add/remove/query jenkins slaves + * Ability to retrieve/find builds by subversion revision + +Deletion: + +I've completely removed the test methods and the small amount of test cases +All tests were tied to a specific local environment. + +Thanx to salimfadhley@gmail.com for the initial version which we based this on +Thanx to ruslan@hyves.nl for the subversion revision patches + +Current code lives on github: https://github.com/ramonvanalteren/jenkinsapi.git + diff --git a/TODO b/TODO new file mode 100644 index 0000000..6444c2d --- /dev/null +++ b/TODO @@ -0,0 +1,9 @@ +TODO: + +* Add a testsuite (preferably nose or py.test) which doesn't rely on a local jenkins setup (or instantiates one during test) +* Clean up the fingerprint code +* Clean up the resultset and results code +* Make all objects inherit off jenkins_base where that makes sense +* Add ability to add/modify/delete jobs +* Add ability to query jenkins for plugin data + diff --git a/pyjenkinsci/utils/__init__.py b/jenkinsapi/__init__.py similarity index 100% rename from pyjenkinsci/utils/__init__.py rename to jenkinsapi/__init__.py diff --git a/pyjenkinsci/api.py b/jenkinsapi/api.py similarity index 93% rename from pyjenkinsci/api.py rename to jenkinsapi/api.py index bd3dd61..a8a5be8 100644 --- a/pyjenkinsci/api.py +++ b/jenkinsapi/api.py @@ -1,137 +1,139 @@ -from pyjenkinsci.artifact import Artifact -from pyjenkinsci import constants -from pyjenkinsci.jenkins import Jenkins -from pyjenkinsci.exceptions import ArtifactsMissing, TimeOut, BadURL -from urllib2 import urlparse - -import os -import time -import logging - -log = logging.getLogger(__name__) - -def get_latest_test_results( jenkinsurl, jobname ): - """ - A convenience function to fetch down the very latest test results from a jenkins job. - """ - latestbuild = get_latest_build( jenkinsurl, jobname ) - res = latestbuild.get_resultset() - return res - -def get_latest_build(jenkinsurl, jobname): - """ - A convenience function to fetch down the very latest test results from a jenkins job. - """ - jenkinsci = Jenkins(jenkinsurl) - job = jenkinsci[jobname] - return job.get_last_build() - -def get_latest_complete_build(jenkinsurl, jobname): - """ - A convenience function to fetch down the very latest test results from a jenkins job. - """ - jenkinsci = Jenkins(jenkinsurl) - job = jenkinsci[jobname] - return job.get_last_completed_build() - -def get_artifacts( jenkinsurl, jobid=None, build_no=None, proxyhost=None, proxyport=None, proxyuser=None, proxypass=None ): - """ - Find all the artifacts for the latest build of a job. - """ - jenkinsci = Jenkins(jenkinsurl, proxyhost, proxyport, proxyuser, proxypass) - job = jenkinsci[jobid] - if build_no: - build = job.get_build( build_no ) - else: - build = job.get_last_good_build() - artifacts = dict((artifact.filename, artifact) for artifact in build.get_artifacts()) - log.info("Found %i artifacts in '%s'" % ( len(artifacts.keys() ), build_no )) - return artifacts - -def search_artifacts(jenkinsurl, jobid, artifact_ids=None, same_build=True, build_search_limit=None): - """ - Search the entire history of a jenkins job for a list of artifact names. If same_build - is true then ensure that all artifacts come from the same build of the job - """ - if len(artifact_ids) == 0 or artifact_ids is None: - return [] - assert same_build, "same_build==False not supported yet" - jenkinsci = Jenkins( jenkinsurl ) - job = jenkinsci[ jobid ] - build_ids = job.get_build_ids() - for build_id in build_ids: - build = job.get_build( build_id ) - artifacts = build.get_artifact_dict() - if set( artifact_ids ).issubset( set( artifacts.keys() ) ): - return dict( ( a,artifacts[a] ) for a in artifact_ids ) - missing_artifacts = set( artifact_ids ) - set( artifacts.keys() ) - log.debug("Artifacts %s missing from %s #%i" % ( ", ".join( missing_artifacts ), jobid, build_id )) - raise ArtifactsMissing( missing_artifacts ) - -def grab_artifact(jenkinsurl, jobid, artifactid, targetdir): - """ - Convenience method to find the latest good version of an artifact and save it - to a target directory. Directory is made automatically if not exists. - """ - artifacts = get_artifacts( jenkinsurl, jobid ) - artifact = artifacts[ artifactid ] - if not os.path.exists( targetdir ): - os.makedirs( targetdir ) - artifact.savetodir( targetdir) - -def block_until_complete(jenkinsurl, jobs, maxwait=12000, interval=30, raise_on_timeout=True): - """ - Wait until all of the jobs in the list are complete. - """ - assert maxwait > 0 - assert maxwait > interval - assert interval > 0 - - obj_jenkins = Jenkins(jenkinsurl) - obj_jobs = [obj_jenkins[jid] for jid in jobs] - for time_left in xrange(maxwait, 0, -interval): - still_running = [j for j in obj_jobs if j.is_queued_or_running()] - if not still_running: - return - str_still_running = ", ".join('"%s"' % str(a) for a in still_running) - log.warn( "Waiting for jobs %s to complete. Will wait another %is" % (str_still_running, time_left )) - time.sleep(interval) - if raise_on_timeout: - raise TimeOut("Waited too long for these jobs to complete: %s" % str_still_running) - -def get_view_from_url(url): - """ - Factory method - """ - matched = constants.RE_SPLIT_VIEW_URL.search(url) - if not matched: - raise BadURL("Cannot parse URL %s" % url) - jenkinsurl, view_name = matched.groups() - jenkinsci = Jenkins(jenkinsurl) - return jenkinsci.get_view(view_name) - -def install_artifacts(artifacts, dirstruct, installdir, basestaticurl): - """ - Install the artifacts. - """ - assert basestaticurl.endswith("/"), "Basestaticurl should end with /" - installed = [] - for reldir, artifactnames in dirstruct.items(): - destdir = os.path.join(installdir, reldir) - if not os.path.exists(destdir): - log.warn("Making install directory %s" % destdir) - os.makedirs(destdir) - else: - assert os.path.isdir(destdir) - for artifactname in artifactnames: - destpath = os.path.abspath(os.path.join( destdir, artifactname)) - if artifactname in artifacts.keys(): - # The artifact must be loaded from jenkins - theartifact = artifacts[artifactname] - else: - # It's probably a static file, we can get it from the static collection - staticurl = urlparse.urljoin(basestaticurl, artifactname) - theartifact = Artifact(artifactname, staticurl) - theartifact.save(destpath) - installed.append(destpath) - return installed +from jenkinsapi.artifact import Artifact +from jenkinsapi import constants +from jenkinsapi.jenkins import Jenkins +from jenkinsapi.exceptions import ArtifactsMissing, TimeOut, BadURL +from urllib2 import urlparse + +import os +import time +import logging + +log = logging.getLogger(__name__) + +def get_latest_test_results( jenkinsurl, jobname ): + """ + A convenience function to fetch down the very latest test results from a jenkins job. + """ + latestbuild = get_latest_build( jenkinsurl, jobname ) + res = latestbuild.get_resultset() + return res + +def get_latest_build(jenkinsurl, jobname): + """ + A convenience function to fetch down the very latest test results from a jenkins job. + """ + jenkinsci = Jenkins(jenkinsurl) + job = jenkinsci[jobname] + return job.get_last_build() + +def get_latest_complete_build(jenkinsurl, jobname): + """ + A convenience function to fetch down the very latest test results from a jenkins job. + """ + jenkinsci = Jenkins(jenkinsurl) + job = jenkinsci[jobname] + return job.get_last_completed_build() + +def get_artifacts( jenkinsurl, jobid=None, build_no=None, proxyhost=None, proxyport=None, proxyuser=None, proxypass=None ): + """ + Find all the artifacts for the latest build of a job. + """ + jenkinsci = Jenkins(jenkinsurl, proxyhost, proxyport, proxyuser, proxypass) + job = jenkinsci[jobid] + if build_no: + build = job.get_build( build_no ) + else: + build = job.get_last_good_build() + artifacts = dict((artifact.filename, artifact) for artifact in build.get_artifacts()) + log.info("Found %i artifacts in '%s'" % ( len(artifacts.keys() ), build_no )) + return artifacts + +def search_artifacts(jenkinsurl, jobid, artifact_ids=None, same_build=True): + """ + Search the entire history of a jenkins job for a list of artifact names. If same_build + is true then ensure that all artifacts come from the same build of the job + """ + if len(artifact_ids) == 0 or artifact_ids is None: + return [] + assert same_build, "same_build==False not supported yet" + jenkinsci = Jenkins( jenkinsurl ) + job = jenkinsci[ jobid ] + build_ids = job.get_build_ids() + for build_id in build_ids: + build = job.get_build( build_id ) + artifacts = build.get_artifact_dict() + if set( artifact_ids ).issubset( set( artifacts.keys() ) ): + return dict( ( a,artifacts[a] ) for a in artifact_ids ) + missing_artifacts = set( artifact_ids ) - set( artifacts.keys() ) + log.debug("Artifacts %s missing from %s #%i" % ( ", ".join( missing_artifacts ), jobid, build_id )) + #noinspection PyUnboundLocalVariable + raise ArtifactsMissing( missing_artifacts ) + +def grab_artifact(jenkinsurl, jobid, artifactid, targetdir): + """ + Convenience method to find the latest good version of an artifact and save it + to a target directory. Directory is made automatically if not exists. + """ + artifacts = get_artifacts( jenkinsurl, jobid ) + artifact = artifacts[ artifactid ] + if not os.path.exists( targetdir ): + os.makedirs( targetdir ) + artifact.savetodir( targetdir) + +def block_until_complete(jenkinsurl, jobs, maxwait=12000, interval=30, raise_on_timeout=True): + """ + Wait until all of the jobs in the list are complete. + """ + assert maxwait > 0 + assert maxwait > interval + assert interval > 0 + + obj_jenkins = Jenkins(jenkinsurl) + obj_jobs = [obj_jenkins[jid] for jid in jobs] + for time_left in xrange(maxwait, 0, -interval): + still_running = [j for j in obj_jobs if j.is_queued_or_running()] + if not still_running: + return + str_still_running = ", ".join('"%s"' % str(a) for a in still_running) + log.warn( "Waiting for jobs %s to complete. Will wait another %is" % (str_still_running, time_left )) + time.sleep(interval) + if raise_on_timeout: + #noinspection PyUnboundLocalVariable + raise TimeOut("Waited too long for these jobs to complete: %s" % str_still_running) + +def get_view_from_url(url): + """ + Factory method + """ + matched = constants.RE_SPLIT_VIEW_URL.search(url) + if not matched: + raise BadURL("Cannot parse URL %s" % url) + jenkinsurl, view_name = matched.groups() + jenkinsci = Jenkins(jenkinsurl) + return jenkinsci.get_view(view_name) + +def install_artifacts(artifacts, dirstruct, installdir, basestaticurl): + """ + Install the artifacts. + """ + assert basestaticurl.endswith("/"), "Basestaticurl should end with /" + installed = [] + for reldir, artifactnames in dirstruct.items(): + destdir = os.path.join(installdir, reldir) + if not os.path.exists(destdir): + log.warn("Making install directory %s" % destdir) + os.makedirs(destdir) + else: + assert os.path.isdir(destdir) + for artifactname in artifactnames: + destpath = os.path.abspath(os.path.join( destdir, artifactname)) + if artifactname in artifacts.keys(): + # The artifact must be loaded from jenkins + theartifact = artifacts[artifactname] + else: + # It's probably a static file, we can get it from the static collection + staticurl = urlparse.urljoin(basestaticurl, artifactname) + theartifact = Artifact(artifactname, staticurl) + theartifact.save(destpath) + installed.append(destpath) + return installed diff --git a/pyjenkinsci/artifact.py b/jenkinsapi/artifact.py similarity index 96% rename from pyjenkinsci/artifact.py rename to jenkinsapi/artifact.py index dda35a5..cca60af 100644 --- a/pyjenkinsci/artifact.py +++ b/jenkinsapi/artifact.py @@ -4,8 +4,8 @@ import os import logging import hashlib -from pyjenkinsci.exceptions import ArtifactBroken -from pyjenkinsci.fingerprint import Fingerprint +from jenkinsapi.exceptions import ArtifactBroken +from jenkinsapi.fingerprint import Fingerprint log = logging.getLogger( __name__ ) diff --git a/pyjenkinsci/build.py b/jenkinsapi/build.py similarity index 87% rename from pyjenkinsci/build.py rename to jenkinsapi/build.py index a3799bc..887ec7c 100644 --- a/pyjenkinsci/build.py +++ b/jenkinsapi/build.py @@ -1,112 +1,112 @@ -from pyjenkinsci.artifact import Artifact -from pyjenkinsci import config -from pyjenkinsci.jenkinsbase import JenkinsBase -from pyjenkinsci.exceptions import NoResults, FailedNoResults -from pyjenkinsci.constants import STATUS_FAIL, STATUS_ABORTED, RESULTSTATUS_FAILURE -from pyjenkinsci.result_set import ResultSet - -from datetime import time -import logging - -log = logging.getLogger(__name__) - -class Build(JenkinsBase): - """ - Represents a jenkins build, executed in context of a job. - """ - - STR_TOTALCOUNT = "totalCount" - STR_TPL_NOTESTS_ERR = "%s has status %s, and does not have any test results" - - def __init__( self, url, buildno, job ): - assert type(buildno) == int - self.buildno = buildno - self.job = job - JenkinsBase.__init__( self, url ) - - def __str__(self): - return self._data['fullDisplayName'] - - def id(self): - return self._data["number"] - - def get_status(self): - return self._data["result"] - - def get_revision(self): - for set in self._data["changeSet"]["revisions"]: - return set["revision"] - - def get_duration(self): - return self._data["duration"] - - def get_artifacts( self ): - for afinfo in self._data["artifacts"]: - url = "%sartifact/%s" % ( self.baseurl, afinfo["relativePath"] ) - af = Artifact( afinfo["fileName"], url, self ) - yield af - del af, url - - def get_artifact_dict(self): - return dict( (a.filename, a) for a in self.get_artifacts() ) - - def is_running( self ): - """ - Return a bool if running. - """ - self.poll() - return self._data["building"] - - def is_good( self ): - """ - Return a bool, true if the build was good. - If the build is still running, return False. - """ - return ( not self.is_running() ) and self._data["result"] == 'SUCCESS' - - def block_until_complete(self, delay=15): - assert isinstance( delay, int ) - count = 0 - while self.is_running(): - total_wait = delay * count - log.info("Waited %is for %s #%s to complete" % ( total_wait, self.job.id(), self.id() ) ) - time.sleep( delay ) - count += 1 - - def get_jenkins_obj(self): - return self.job.get_jenkins_obj() - - def get_result_url(self): - """ - Return the URL for the object which provides the job's result summary. - """ - url_tpl = r"%stestReport/%s" - return url_tpl % ( self._data["url"] , config.JENKINS_API ) - - def get_resultset(self): - """ - Obtain detailed results for this build. - """ - result_url = self.get_result_url() - if self.STR_TOTALCOUNT not in self.get_actions(): - raise NoResults( "%s does not have any published results" % str(self) ) - buildstatus = self.get_status() - if buildstatus in [ STATUS_FAIL, RESULTSTATUS_FAILURE, STATUS_ABORTED ]: - raise FailedNoResults( self.STR_TPL_NOTESTS_ERR % ( str(self), buildstatus ) ) - if self.get_actions()[ self.STR_TOTALCOUNT ] == 0: - raise NoResults( self.STR_TPL_NOTESTS_ERR % ( str(self), buildstatus ) ) - obj_results = ResultSet( result_url, build=self ) - return obj_results - - def has_resultset(self): - """ - Return a boolean, true if a result set is available. false if not. - """ - return self.STR_TOTALCOUNT in self.get_actions() - - def get_actions(self): - all_actions = {} - for dct_action in self._data["actions"]: - all_actions.update( dct_action ) - return all_actions - +from jenkinsapi.artifact import Artifact +from jenkinsapi import config +from jenkinsapi.jenkinsbase import JenkinsBase +from jenkinsapi.exceptions import NoResults, FailedNoResults +from jenkinsapi.constants import STATUS_FAIL, STATUS_ABORTED, RESULTSTATUS_FAILURE +from jenkinsapi.result_set import ResultSet + +from datetime import time +import logging + +log = logging.getLogger(__name__) + +class Build(JenkinsBase): + """ + Represents a jenkins build, executed in context of a job. + """ + + STR_TOTALCOUNT = "totalCount" + STR_TPL_NOTESTS_ERR = "%s has status %s, and does not have any test results" + + def __init__( self, url, buildno, job ): + assert type(buildno) == int + self.buildno = buildno + self.job = job + JenkinsBase.__init__( self, url ) + + def __str__(self): + return self._data['fullDisplayName'] + + def id(self): + return self._data["number"] + + def get_status(self): + return self._data["result"] + + def get_revision(self): + for set in self._data["changeSet"]["revisions"]: + return set["revision"] + + def get_duration(self): + return self._data["duration"] + + def get_artifacts( self ): + for afinfo in self._data["artifacts"]: + url = "%sartifact/%s" % ( self.baseurl, afinfo["relativePath"] ) + af = Artifact( afinfo["fileName"], url, self ) + yield af + del af, url + + def get_artifact_dict(self): + return dict( (a.filename, a) for a in self.get_artifacts() ) + + def is_running( self ): + """ + Return a bool if running. + """ + self.poll() + return self._data["building"] + + def is_good( self ): + """ + Return a bool, true if the build was good. + If the build is still running, return False. + """ + return ( not self.is_running() ) and self._data["result"] == 'SUCCESS' + + def block_until_complete(self, delay=15): + assert isinstance( delay, int ) + count = 0 + while self.is_running(): + total_wait = delay * count + log.info("Waited %is for %s #%s to complete" % ( total_wait, self.job.id(), self.id() ) ) + time.sleep( delay ) + count += 1 + + def get_jenkins_obj(self): + return self.job.get_jenkins_obj() + + def get_result_url(self): + """ + Return the URL for the object which provides the job's result summary. + """ + url_tpl = r"%stestReport/%s" + return url_tpl % ( self._data["url"] , config.JENKINS_API ) + + def get_resultset(self): + """ + Obtain detailed results for this build. + """ + result_url = self.get_result_url() + if self.STR_TOTALCOUNT not in self.get_actions(): + raise NoResults( "%s does not have any published results" % str(self) ) + buildstatus = self.get_status() + if buildstatus in [ STATUS_FAIL, RESULTSTATUS_FAILURE, STATUS_ABORTED ]: + raise FailedNoResults( self.STR_TPL_NOTESTS_ERR % ( str(self), buildstatus ) ) + if not self.get_actions()[self.STR_TOTALCOUNT]: + raise NoResults( self.STR_TPL_NOTESTS_ERR % ( str(self), buildstatus ) ) + obj_results = ResultSet( result_url, build=self ) + return obj_results + + def has_resultset(self): + """ + Return a boolean, true if a result set is available. false if not. + """ + return self.STR_TOTALCOUNT in self.get_actions() + + def get_actions(self): + all_actions = {} + for dct_action in self._data["actions"]: + all_actions.update( dct_action ) + return all_actions + diff --git a/pyjenkinsci/command_line/__init__.py b/jenkinsapi/command_line/__init__.py similarity index 100% rename from pyjenkinsci/command_line/__init__.py rename to jenkinsapi/command_line/__init__.py diff --git a/pyjenkinsci/command_line/jenkins_invoke.py b/jenkinsapi/command_line/jenkins_invoke.py similarity index 97% rename from pyjenkinsci/command_line/jenkins_invoke.py rename to jenkinsapi/command_line/jenkins_invoke.py index 54ad596..d725d28 100644 --- a/pyjenkinsci/command_line/jenkins_invoke.py +++ b/jenkinsapi/command_line/jenkins_invoke.py @@ -2,7 +2,7 @@ import os import sys import logging import optparse -from pyjenkinsci import jenkins +from jenkinsapi import jenkins log = logging.getLogger(__name__) diff --git a/pyjenkinsci/config.py b/jenkinsapi/config.py similarity index 71% rename from pyjenkinsci/config.py rename to jenkinsapi/config.py index aba8f70..e998c2f 100644 --- a/pyjenkinsci/config.py +++ b/jenkinsapi/config.py @@ -1,3 +1,3 @@ -JENKINS_API = r"api/python/" -LOAD_TIMEOUT = 30 +JENKINS_API = r"api/python/" +LOAD_TIMEOUT = 30 LOAD_ATTEMPTS = 5 \ No newline at end of file diff --git a/pyjenkinsci/constants.py b/jenkinsapi/constants.py similarity index 95% rename from pyjenkinsci/constants.py rename to jenkinsapi/constants.py index d456dff..34a9348 100644 --- a/pyjenkinsci/constants.py +++ b/jenkinsapi/constants.py @@ -1,15 +1,15 @@ -import re - -STATUS_FAIL = "FAIL" -STATUS_ERROR = "ERROR" -STATUS_ABORTED = "ABORTED" -STATUS_REGRESSION = "REGRESSION" - -STATUS_FIXED = "FIXED" -STATUS_PASSED = "PASSED" - -RESULTSTATUS_FAILURE = "FAILURE" -RESULTSTATUS_FAILED = "FAILED" - -STR_RE_SPLIT_VIEW = "(.*)/view/([^/]*)/?" -RE_SPLIT_VIEW_URL = re.compile( STR_RE_SPLIT_VIEW ) +import re + +STATUS_FAIL = "FAIL" +STATUS_ERROR = "ERROR" +STATUS_ABORTED = "ABORTED" +STATUS_REGRESSION = "REGRESSION" + +STATUS_FIXED = "FIXED" +STATUS_PASSED = "PASSED" + +RESULTSTATUS_FAILURE = "FAILURE" +RESULTSTATUS_FAILED = "FAILED" + +STR_RE_SPLIT_VIEW = "(.*)/view/([^/]*)/?" +RE_SPLIT_VIEW_URL = re.compile( STR_RE_SPLIT_VIEW ) diff --git a/pyjenkinsci/exceptions.py b/jenkinsapi/exceptions.py similarity index 100% rename from pyjenkinsci/exceptions.py rename to jenkinsapi/exceptions.py diff --git a/pyjenkinsci/fingerprint.py b/jenkinsapi/fingerprint.py similarity index 89% rename from pyjenkinsci/fingerprint.py rename to jenkinsapi/fingerprint.py index f9c00d6..54abfab 100644 --- a/pyjenkinsci/fingerprint.py +++ b/jenkinsapi/fingerprint.py @@ -1,74 +1,74 @@ -from pyjenkinsci.jenkinsbase import JenkinsBase -from pyjenkinsci.exceptions import ArtifactBroken - -import urllib2 -import re - -import logging - -log = logging.getLogger( __name__ ) - -class Fingerprint(JenkinsBase): - """ - Represents a jenkins fingerprint on a single artifact file ?? - """ - RE_MD5 = re.compile("^([0-9a-z]{32})$") - - def __init__(self, baseurl, id, jenkins_obj): - logging.basicConfig() - self.jenkins_obj = jenkins_obj - assert self.RE_MD5.search( id ), "%s does not look like a valid id" % id - url = "%s/fingerprint/%s/" % ( baseurl, id ) - JenkinsBase.__init__( self, url, poll=False ) - self.id = id - - def get_jenkins_obj(self): - return self.jenkins_obj - - def __str__(self): - return self.id - - def valid(self): - """ - Return True / False if valid - """ - try: - self.poll() - except urllib2.HTTPError, e: - return False - return True - - def validate_for_build(self, filename, job, build): - if not self.valid(): - log.info("Unknown to jenkins.") - return False - if not self._data["original"] is None: - if self._data["original"]["name"] == job: - if self._data["original"]["number"] == build: - return True - if self._data["fileName"] != filename: - log.info("Filename from jenkins (%s) did not match provided (%s)" % ( self._data["fileName"], filename ) ) - return False - for usage_item in self._data["usage"]: - if usage_item["name"] == job: - for range in usage_item["ranges"]["ranges"]: - if range["start"] <= build <= range["end"]: - log.info("This artifact was generated by %s between build %i and %i" % ( job, range["start"], range["end"] ) ) - return True - return False - - def validate(self): - try: - assert self.valid() - except AssertionError, ae: - raise ArtifactBroken( "Artifact %s seems to be broken, check %s" % ( self.id, self.baseurl ) ) - except urllib2.HTTPError, httpe: - raise ArtifactBroken( "Unable to validate artifact id %s using %s" % ( self.id, self.baseurl ) ) - return True - - def get_info( self ): - """ - Returns a tuple of build-name, build# and artifiact filename for a good build. - """ - self.poll() - return self._data["original"]["name"], self._data["original"]["number"], self._data["fileName"] +from jenkinsapi.jenkinsbase import JenkinsBase +from jenkinsapi.exceptions import ArtifactBroken + +import urllib2 +import re + +import logging + +log = logging.getLogger( __name__ ) + +class Fingerprint(JenkinsBase): + """ + Represents a jenkins fingerprint on a single artifact file ?? + """ + RE_MD5 = re.compile("^([0-9a-z]{32})$") + + def __init__(self, baseurl, id, jenkins_obj): + logging.basicConfig() + self.jenkins_obj = jenkins_obj + assert self.RE_MD5.search( id ), "%s does not look like a valid id" % id + url = "%s/fingerprint/%s/" % ( baseurl, id ) + JenkinsBase.__init__( self, url, poll=False ) + self.id = id + + def get_jenkins_obj(self): + return self.jenkins_obj + + def __str__(self): + return self.id + + def valid(self): + """ + Return True / False if valid + """ + try: + self.poll() + except urllib2.HTTPError: + return False + return True + + def validate_for_build(self, filename, job, build): + if not self.valid(): + log.info("Unknown to jenkins.") + return False + if not self._data["original"] is None: + if self._data["original"]["name"] == job: + if self._data["original"]["number"] == build: + return True + if self._data["fileName"] != filename: + log.info("Filename from jenkins (%s) did not match provided (%s)" % ( self._data["fileName"], filename ) ) + return False + for usage_item in self._data["usage"]: + if usage_item["name"] == job: + for range in usage_item["ranges"]["ranges"]: + if range["start"] <= build <= range["end"]: + log.info("This artifact was generated by %s between build %i and %i" % ( job, range["start"], range["end"] ) ) + return True + return False + + def validate(self): + try: + assert self.valid() + except AssertionError: + raise ArtifactBroken( "Artifact %s seems to be broken, check %s" % ( self.id, self.baseurl ) ) + except urllib2.HTTPError: + raise ArtifactBroken( "Unable to validate artifact id %s using %s" % ( self.id, self.baseurl ) ) + return True + + def get_info( self ): + """ + Returns a tuple of build-name, build# and artifiact filename for a good build. + """ + self.poll() + return self._data["original"]["name"], self._data["original"]["number"], self._data["fileName"] diff --git a/pyjenkinsci/jenkins.py b/jenkinsapi/jenkins.py similarity index 94% rename from pyjenkinsci/jenkins.py rename to jenkinsapi/jenkins.py index ddd8b72..4060337 100644 --- a/pyjenkinsci/jenkins.py +++ b/jenkinsapi/jenkins.py @@ -1,9 +1,9 @@ -from pyjenkinsci.jenkinsbase import JenkinsBase -from pyjenkinsci.fingerprint import Fingerprint -from pyjenkinsci.job import Job -from pyjenkinsci.view import View -from pyjenkinsci.node import Node -from pyjenkinsci.exceptions import UnknownJob +from jenkinsapi.jenkinsbase import JenkinsBase +from jenkinsapi.fingerprint import Fingerprint +from jenkinsapi.job import Job +from jenkinsapi.view import View +from jenkinsapi.node import Node +from jenkinsapi.exceptions import UnknownJob from utils.urlopener import mkurlopener import logging import time @@ -115,7 +115,8 @@ class Jenkins(JenkinsBase): try: view_dict = self.get_view_dict() return view_dict[ str_view_name ] - except KeyError, ke: + except KeyError: + #noinspection PyUnboundLocalVariable all_views = ", ".join( view_dict.keys() ) raise KeyError("View %s is not known - available: %s" % ( str_view_name, all_views ) ) @@ -174,10 +175,10 @@ class Jenkins(JenkinsBase): url = "%s/doDelete" % self.get_node_url(nodename) fn_urlopen = self.get_jenkins_obj().get_opener() try: - stream = fn_urlopen(url) - html_result = stream.read() + fn_urlopen(url).read() except urllib2.HTTPError, e: log.debug("Error reading %s" % url) + log.exception(e) raise return not self.has_node(nodename) @@ -223,8 +224,7 @@ class Jenkins(JenkinsBase): print url fn_urlopen = self.get_jenkins_obj().get_opener() try: - stream = fn_urlopen(url) - html_result = stream.read() + fn_urlopen(url).read() except urllib2.HTTPError, e: log.debug("Error reading %s" % url) log.exception(e) diff --git a/pyjenkinsci/jenkinsbase.py b/jenkinsapi/jenkinsbase.py similarity index 92% rename from pyjenkinsci/jenkinsbase.py rename to jenkinsapi/jenkinsbase.py index 0d7be0b..38a759d 100644 --- a/pyjenkinsci/jenkinsbase.py +++ b/jenkinsapi/jenkinsbase.py @@ -1,72 +1,73 @@ -import urllib2 -import logging -import pprint -from pyjenkinsci import config -from pyjenkinsci.utils.retry import retry_function - -log = logging.getLogger( __name__ ) - -class JenkinsBase(object): - """ - This appears to be the base object that all other jenkins objects are inherited from - """ - RETRY_ATTEMPTS = 5 - - def __repr__( self ): - return """<%s.%s %s>""" % ( self.__class__.__module__, - self.__class__.__name__, - str( self ) ) - - def print_data(self): - pprint.pprint( self._data ) - - def __str__(self): - raise NotImplemented - - def __init__( self, baseurl, poll=True ): - """ - Initialize a jenkins connection - """ - self.baseurl = baseurl - if poll: - try: - self.poll() - except urllib2.HTTPError, hte: - log.exception(hte) - log.warn( "Failed to conenct to %s" % baseurl ) - raise - - def poll(self): - self._data = self._poll() - - def _poll(self): - url = self.python_api_url( self.baseurl ) - return retry_function( self.RETRY_ATTEMPTS , self.get_data, url ) - - def get_jenkins_obj(self): - """Not implemented, abstract method implemented by child classes""" - raise NotImplemented("Abstract method, implemented by child classes") - - @classmethod - def python_api_url( cls, url ): - if url.endswith( config.JENKINS_API ): - return url - else: - if url.endswith( r"/" ): - fmt="%s%s" - else: - fmt = "%s/%s" - return fmt % (url, config.JENKINS_API) - - def get_data( self, url ): - """ - Find out how to connect, and then grab the data. - """ - fn_urlopen = self.get_jenkins_obj().get_opener() - try: - stream = fn_urlopen( url ) - result = eval( stream.read() ) - except urllib2.HTTPError, e: - log.warn( "Error reading %s" % url ) - raise - return result +import urllib2 +import logging +import pprint +from jenkinsapi import config +from jenkinsapi.utils.retry import retry_function + +log = logging.getLogger( __name__ ) + +class JenkinsBase(object): + """ + This appears to be the base object that all other jenkins objects are inherited from + """ + RETRY_ATTEMPTS = 5 + + def __repr__( self ): + return """<%s.%s %s>""" % ( self.__class__.__module__, + self.__class__.__name__, + str( self ) ) + + def print_data(self): + pprint.pprint( self._data ) + + def __str__(self): + raise NotImplemented + + def __init__( self, baseurl, poll=True ): + """ + Initialize a jenkins connection + """ + self.baseurl = baseurl + if poll: + try: + self.poll() + except urllib2.HTTPError, hte: + log.exception(hte) + log.warn( "Failed to conenct to %s" % baseurl ) + raise + + def poll(self): + self._data = self._poll() + + def _poll(self): + url = self.python_api_url( self.baseurl ) + return retry_function( self.RETRY_ATTEMPTS , self.get_data, url ) + + def get_jenkins_obj(self): + """Not implemented, abstract method implemented by child classes""" + raise NotImplemented("Abstract method, implemented by child classes") + + @classmethod + def python_api_url( cls, url ): + if url.endswith( config.JENKINS_API ): + return url + else: + if url.endswith( r"/" ): + fmt="%s%s" + else: + fmt = "%s/%s" + return fmt % (url, config.JENKINS_API) + + def get_data( self, url ): + """ + Find out how to connect, and then grab the data. + """ + fn_urlopen = self.get_jenkins_obj().get_opener() + try: + stream = fn_urlopen( url ) + result = eval( stream.read() ) + except urllib2.HTTPError, e: + log.warn( "Error reading %s" % url ) + log.exception(e) + raise + return result diff --git a/pyjenkinsci/job.py b/jenkinsapi/job.py similarity index 98% rename from pyjenkinsci/job.py rename to jenkinsapi/job.py index 0ed7ce9..dfb6faf 100644 --- a/pyjenkinsci/job.py +++ b/jenkinsapi/job.py @@ -3,8 +3,8 @@ import urlparse import urllib2 from collections import defaultdict from datetime import time -from pyjenkinsci.build import Build -from pyjenkinsci.jenkinsbase import JenkinsBase +from jenkinsapi.build import Build +from jenkinsapi.jenkinsbase import JenkinsBase from exceptions import NoBuildData, NotFound @@ -46,6 +46,7 @@ class Job(JenkinsBase): html_result = stream.read() except urllib2.HTTPError, e: log.debug( "Error reading %s" % url ) + log.exception(e) raise return html_result diff --git a/pyjenkinsci/node.py b/jenkinsapi/node.py similarity index 95% rename from pyjenkinsci/node.py rename to jenkinsapi/node.py index 7c0a00c..8a2c1bb 100644 --- a/pyjenkinsci/node.py +++ b/jenkinsapi/node.py @@ -1,4 +1,4 @@ -from pyjenkinsci.jenkinsbase import JenkinsBase +from jenkinsapi.jenkinsbase import JenkinsBase import logging log = logging.getLogger(__name__) diff --git a/pyjenkinsci/result.py b/jenkinsapi/result.py similarity index 96% rename from pyjenkinsci/result.py rename to jenkinsapi/result.py index d02a01c..219d0cc 100644 --- a/pyjenkinsci/result.py +++ b/jenkinsapi/result.py @@ -1,21 +1,21 @@ -class Result(object): - def __init__(self, **kwargs ): - """ - - """ - self.__dict__.update( kwargs ) - - def __str__(self): - return "%s %s %s" % ( self.className, self.name, self.status ) - - def __repr__(self): - module_name = self.__class__.__module__ - class_name = self.__class__.__name__ - self_str = str( self ) - return "<%s.%s %s>" % ( module_name , class_name , self_str ) - - def id(self): - """ - Calculate an ID for this object. - """ - return "%s.%s" % ( self.className, self.name ) +class Result(object): + def __init__(self, **kwargs ): + """ + + """ + self.__dict__.update( kwargs ) + + def __str__(self): + return "%s %s %s" % ( self.className, self.name, self.status ) + + def __repr__(self): + module_name = self.__class__.__module__ + class_name = self.__class__.__name__ + self_str = str( self ) + return "<%s.%s %s>" % ( module_name , class_name , self_str ) + + def id(self): + """ + Calculate an ID for this object. + """ + return "%s.%s" % ( self.className, self.name ) diff --git a/pyjenkinsci/result_set.py b/jenkinsapi/result_set.py similarity index 78% rename from pyjenkinsci/result_set.py rename to jenkinsapi/result_set.py index 5b25728..e3def36 100644 --- a/pyjenkinsci/result_set.py +++ b/jenkinsapi/result_set.py @@ -1,18 +1,21 @@ -from pyjenkinsci.jenkinsbase import JenkinsBase -from pyjenkinsci.result import Result +from jenkinsapi.jenkinsbase import JenkinsBase +from jenkinsapi.result import Result class ResultSet(JenkinsBase): """ Represents a result from a completed Jenkins run. """ - def get_jenkins_obj(self): - return self.build.job.get_jenkins_obj() - def __init__(self, url, build ): """ + Init a resultset + :param url: url for a build, str + :param build: build obj """ self.build = build - JenkinsBase.__init__( self, url ) + JenkinsBase.__init__(self, url) + + def get_jenkins_obj(self): + return self.build.job.get_jenkins_obj() def __str__(self): return "Test Result for %s" % str( self.build ) @@ -36,4 +39,4 @@ class ResultSet(JenkinsBase): yield R.id(), R def __len__(self): - return sum( 1 for x in self.iteritems() ) + return len(self.items()) diff --git a/pyjenkinsci/__init__.py b/jenkinsapi/utils/__init__.py similarity index 100% rename from pyjenkinsci/__init__.py rename to jenkinsapi/utils/__init__.py diff --git a/pyjenkinsci/utils/retry.py b/jenkinsapi/utils/retry.py similarity index 97% rename from pyjenkinsci/utils/retry.py rename to jenkinsapi/utils/retry.py index 6624cfe..334d5c8 100644 --- a/pyjenkinsci/utils/retry.py +++ b/jenkinsapi/utils/retry.py @@ -31,7 +31,7 @@ def retry_function( tries, fn, *args, **kwargs ): raise try: fn_name = fn.__name__ - except AttributeError, ae: + except AttributeError: fn_name = "Anonymous Function" log.exception(e) log.warn( "%s failed at attempt %i, trying again." % ( fn_name , attemptno ) ) diff --git a/pyjenkinsci/utils/urlopener.py b/jenkinsapi/utils/urlopener.py similarity index 100% rename from pyjenkinsci/utils/urlopener.py rename to jenkinsapi/utils/urlopener.py diff --git a/pyjenkinsci/view.py b/jenkinsapi/view.py similarity index 88% rename from pyjenkinsci/view.py rename to jenkinsapi/view.py index d245d62..d72656f 100644 --- a/pyjenkinsci/view.py +++ b/jenkinsapi/view.py @@ -1,61 +1,62 @@ -from pyjenkinsci.jenkinsbase import JenkinsBase -from pyjenkinsci.job import Job - -class View(JenkinsBase): - - def __init__(self, url, name, jenkins_obj): - self.name = name - self.jenkins_obj = jenkins_obj - JenkinsBase.__init__(self, url) - - def __str__(self): - return self.name - - def __getitem__(self, str_job_id ): - assert isinstance( str_job_id, str ) - api_url = self.python_api_url( self.get_job_url( str_job_id ) ) - return Job( api_url, str_job_id, self.jenkins_obj ) - - def keys(self): - return self.get_job_dict().keys() - - def iteritems(self): - for name, url in self.get_job_dict().iteritems(): - api_url = self.python_api_url( url ) - yield name, Job( api_url, name, self.jenkins_obj ) - - def values(self): - return [ a[1] for a in self.iteritems() ] - - def items(self): - return [ a for a in self.iteritems() ] - - def _get_jobs( self ): - if not self._data.has_key( "jobs" ): - pass - else: - for viewdict in self._data["jobs"]: - yield viewdict["name"], viewdict["url"] - - def get_job_dict(self): - return dict( self._get_jobs() ) - - def __len__(self): - return len( self.get_job_dict().keys() ) - - def get_job_url( self, str_job_name ): - try: - job_dict = self.get_job_dict() - return job_dict[ str_job_name ] - except KeyError, ke: - all_views = ", ".join( job_dict.keys() ) - raise KeyError("Job %s is not known - available: %s" % ( str_job_name, all_views ) ) - - def get_jenkins_obj(self): - return self.jenkins_obj - - def id(self): - """ - Calculate an ID for this object. - """ +from jenkinsapi.jenkinsbase import JenkinsBase +from jenkinsapi.job import Job + +class View(JenkinsBase): + + def __init__(self, url, name, jenkins_obj): + self.name = name + self.jenkins_obj = jenkins_obj + JenkinsBase.__init__(self, url) + + def __str__(self): + return self.name + + def __getitem__(self, str_job_id ): + assert isinstance( str_job_id, str ) + api_url = self.python_api_url( self.get_job_url( str_job_id ) ) + return Job( api_url, str_job_id, self.jenkins_obj ) + + def keys(self): + return self.get_job_dict().keys() + + def iteritems(self): + for name, url in self.get_job_dict().iteritems(): + api_url = self.python_api_url( url ) + yield name, Job( api_url, name, self.jenkins_obj ) + + def values(self): + return [ a[1] for a in self.iteritems() ] + + def items(self): + return [ a for a in self.iteritems() ] + + def _get_jobs( self ): + if not self._data.has_key( "jobs" ): + pass + else: + for viewdict in self._data["jobs"]: + yield viewdict["name"], viewdict["url"] + + def get_job_dict(self): + return dict( self._get_jobs() ) + + def __len__(self): + return len( self.get_job_dict().keys() ) + + def get_job_url( self, str_job_name ): + try: + job_dict = self.get_job_dict() + return job_dict[ str_job_name ] + except KeyError: + #noinspection PyUnboundLocalVariable + all_views = ", ".join( job_dict.keys() ) + raise KeyError("Job %s is not known - available: %s" % ( str_job_name, all_views ) ) + + def get_jenkins_obj(self): + return self.jenkins_obj + + def id(self): + """ + Calculate an ID for this object. + """ return "%s.%s" % ( self.className, self.name ) \ No newline at end of file diff --git a/setup.py b/setup.py index 55e1ca6..72361ef 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,18 @@ -from setuptools import setup, find_packages - -GLOBAL_ENTRY_POINTS = { - "console_scripts":[ "jenkins_invoke=pyjenkinsci.command_line.hudson_invoke:main", - "meta_test=pyjenkinsci.command_line.meta_test:main", ] } - -setup(name='pyjenkinsci', - version='0.0.35.1', - description='A Python API for accessing resources on a Jenkins continuous-integration server.', - author='Salim Fadhley', - author_email='sal@stodge.org', - package_dir = {'':'pyjenkinsci'}, - packages=find_packages('pyjenkinsci'), - zip_safe=True, - include_package_data = False, - entry_points = GLOBAL_ENTRY_POINTS, - url="https://github.com/ramonvanalteren/pyjenkinsci", - ) +from setuptools import setup, find_packages + +GLOBAL_ENTRY_POINTS = { + "console_scripts":[ "jenkins_invoke=jenkinsapi.command_line.jenkins_invoke:main"] + } + +setup(name='jenkinsapi', + version='0.1', + description='A Python API for accessing resources on a Jenkins continuous-integration server.', + author="Ramon van Alteren", + author_email='ramon@vanalteren.nl', + package_dir = {'':'jenkinsapi'}, + packages=find_packages('jenkinsapi'), + zip_safe=True, + include_package_data = False, + entry_points = GLOBAL_ENTRY_POINTS, + url="https://github.com/ramonvanalteren/jenkinsapi", + )