Created
May 8, 2018 23:53
-
-
Save jondkelley/fea7a4aec969f10113eaa4082ff70e68 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class JenkinsBuilder(object): | |
""" | |
helper class for triggering jenkins jobs with the proper parameters | |
""" | |
def __init__(self, logger, baseurl, user, user_token, job, job_token): | |
self.logger = logger | |
self.baseurl = baseurl | |
self.user = user | |
self.user_token = user_token | |
self.job = job | |
self.job_token = job_token | |
def get_build_from_queue(self, queue_url, retries=0): | |
""" | |
method is called after self.trigger_build() to grab the jobstatus url | |
THIS IS A DANGEROUS RECURSIVE METHOD | |
returns (str) job status | |
""" | |
retries += 1 | |
retry_limit = 90 | |
complete_queue_status_url = "{}/api/json".format(queue_url) | |
queue_item = requests.get( | |
complete_queue_status_url, auth=(self.user, self.user_token)) | |
item_queued = queue_item.json().get("executable", None) | |
if not item_queued: | |
if retries > retry_limit: | |
raise Exception( | |
'Exceeded recursion on get_build_from_queue. Jenkins is too busy.') | |
else: | |
self.logger.debug("A job hasn't queued, next recursion in 5 seconds (Attempt {} out {})".format( | |
str(retries), retry_limit)) | |
sleep(5) | |
return self.get_build_from_queue(queue_url, retries) | |
else: | |
return queue_item.json()['executable']['url'] | |
def is_build_stable(self, job_url, retries=0): | |
""" | |
pings the executable.url for a particular job and validates the build is successful | |
THIS IS A DANGEROUS RECURSIVE METHOD | |
returns (bool) true if stable | |
""" | |
retries += 1 | |
retry_limit = 90 | |
complete_job_status_url = "{}/api/json".format(job_url) | |
job_status = requests.get( | |
complete_job_status_url, auth=(self.user, self.user_token)) | |
build_result = job_status.json().get("result", None) | |
if not build_result: | |
if retries > retry_limit: | |
raise Exception( | |
'Exceeded recursion on is_build_stable. Jenkins is too busy.') | |
else: | |
self.logger.debug("Job not finished yet, next recursion in 10 seconds (Attempt {} out {})".format( | |
str(retries), retry_limit)) | |
sleep(10) | |
return self.is_build_stable(job_url, retries) | |
else: | |
if build_result == "SUCCESS": | |
return True | |
else: | |
return False | |
def build_remote_jenkins_url(self, **parameters): | |
""" | |
concatenate some fields to formulate the jenkins url and parameters and stuff | |
""" | |
build_params = [] | |
for k, v in parameters.items(): | |
build_params.append('&{}={}'.format(k, v)) | |
build_params = "".join(build_params) | |
complete_trigger_url = '{}/job/{}/buildWithParameters?token={}{}'.format( | |
self.baseurl, | |
self.job, | |
self.job_token, | |
build_params) | |
return complete_trigger_url | |
def fetch_crumb_from_jenkins(self, base_jenkins_fqdn, jnk_usr, jnk_token): | |
""" | |
Purpose: create and return csrf crumb header required to trigger remote jenkins build. | |
get crumb token via Jenkins endpoint listed below. This is done in order to | |
comply with a security mechanism that prevents cross-site request forgery. | |
""" | |
crumb_issue_url = "{}/crumbIssuer/api/json".format(base_jenkins_fqdn) | |
try: | |
crumb_response = requests.get( | |
crumb_issue_url, auth=(jnk_usr, jnk_token)) | |
crumb_dict = crumb_response.json() | |
# print("Crumb response: {}".format(crumb_dict)) | |
# Extract the crumb field name and crumb value from response | |
crumb_field = crumb_dict["crumbRequestField"] | |
crumb_value = crumb_dict["crumb"] | |
# print( | |
# ( | |
# "Field ID: {}{}".format(crumb_field, "\n") + | |
# "Crumb: {}".format(crumb_value) | |
# ) | |
# ) | |
except Exception as e: | |
print("Failed to retrieve crumb. Cannot make API call to Jenkins in lieu of this. Exception: {}".format(e)) | |
return False | |
# Adding crumb as header to api request | |
headers = { | |
"{}".format(crumb_field): "{}".format(crumb_value) | |
} | |
# print("Successfully retrieved CSRF crumb/field identifier.") | |
return headers | |
def post_to_jenkins_build_url(self, header_for_crumb, jenkins_trigger_url, jnk_username, jnk_secret): | |
""" | |
sends actual http post to jenkins | |
returns json or none | |
""" | |
# print("Posting to the following url. Note: token is ommitted: {}".format(jenkins_trigger_url.split("?token=")[0])) | |
try: | |
build_post_resp = requests.post( | |
url=jenkins_trigger_url, | |
headers=header_for_crumb, | |
auth=(jnk_username, jnk_secret) | |
) | |
# status code | |
# print(build_post_resp) | |
return build_post_resp | |
except e: | |
print("FAILED to post to Jenkins build url due to an exception: {}".format(e)) | |
return None | |
def trigger_build(self, logger, **parameters): | |
""" | |
fetch a crumb header from jenkins | |
returns true | |
""" | |
crumb_header = self.fetch_crumb_from_jenkins( | |
base_jenkins_fqdn=self.baseurl, | |
jnk_usr=self.user, | |
jnk_token=self.user_token | |
) | |
if crumb_header is None: | |
return | |
elif crumb_header is False: | |
logger.critical( | |
"FAIL - Unable to get csrf token from jenkins." | |
) | |
return | |
# Concatenate the respective Jenkins information variables declared above so that | |
# we have a valid job url | |
complete_trigger_url = self.build_remote_jenkins_url(**parameters) | |
# Now, pass crumb and uploaded file to primary function that makes POST to Jenkins | |
job_POST_results = self.post_to_jenkins_build_url( | |
header_for_crumb=crumb_header, | |
jenkins_trigger_url=complete_trigger_url, | |
jnk_username=self.user, | |
jnk_secret=self.user_token | |
) | |
# recursively ping the queue status until job starts | |
job_queue_url = job_POST_results.headers['Location'] | |
# If the response is none, an error was reported inside the method. | |
if job_POST_results is None: | |
return | |
elif job_POST_results.status_code == 201 or job_POST_results.status_code == 200: | |
# success status code. This is good. | |
# print("success! it triggered Jenkins") | |
return self.get_build_from_queue(job_POST_results.headers['Location']) | |
else: | |
logger.critical( | |
"FAIL - a bad status code was returned from jenkins " + | |
"http status: {} \nresponse:{}".format( | |
job_POST_results.status_code, job_POST_results.text) | |
) | |
return |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment