Skip to content

Instantly share code, notes, and snippets.

@jondkelley
Created May 8, 2018 23:53
Show Gist options
  • Save jondkelley/fea7a4aec969f10113eaa4082ff70e68 to your computer and use it in GitHub Desktop.
Save jondkelley/fea7a4aec969f10113eaa4082ff70e68 to your computer and use it in GitHub Desktop.
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