Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Python script to clean up and update the Jenkins AMIs after a successful Packer run
#!/usr/bin/python
import requests
import os
import boto.ec2
import sys
import re
build_url = os.environ['BUILD_URL']
jenkins_base_url = os.environ['JENKINS_URL']
ami_profile_name = os.environ['AMI_PROFILE_NAME']
jenkins_auth_user = os.environ['JENKINS_AUTH_USER']
jenkins_auth_password = os.environ['JENKINS_AUTH_PASSWORD']
aws_region = os.getenv('AWS_REGION', 'us-east-1')
ec2_cloud_instance = os.getenv('EC2_CLOUD_INSTANCE', 'aws_us-east-1')
output_error_string = os.getenv('OUTPUT_ERROR_STRING', 'Error:')
build_output_text = ""
def get_jenkins_build_output():
global build_url
global build_output_text
if build_output_text:
return build_output_text
if not build_url.endswith('/'):
build_url = '%s/' % build_url
jenkins_url = '%slogText/progressiveText' % build_url
payload = {'start': '1'}
r = requests.post(jenkins_url, verify=False, data=payload)
if not r.status_code == 200:
print 'HTTP POST to Jenkins URL %s resulted in %s' % (jenkins_url, r.status_code)
print r.headers
print r.text
sys.exit(1)
return r.text
def get_error_lines(build_output):
retval = ""
regex = re.compile(r'(.*%s.*)' % output_error_string, re.MULTILINE)
matches = [m.groups() for m in regex.finditer(build_output)]
if matches:
retval = "**************************************************\n"
retval += " Error string: '%s'\n" % output_error_string
retval += " Found the following errors in the build output\n"
retval += "**************************************************\n"
for m in matches:
retval += '%s\n' % m[0]
retval += "**************************************************\n"
return retval
def get_packer_ami_id(build_output):
regex = re.compile(r'.*,amazon-ebs,artifact,.*(ami-.*)$', re.MULTILINE)
matches = [m.groups() for m in regex.finditer(build_output)]
for m in matches:
return m[0].strip()
def delete_ami(ami_id):
ec2_conn = boto.ec2.connect_to_region(aws_region)
ec2_conn.deregister_image(ami_id, delete_snapshot=True)
def get_groovy_url():
groovy_url = jenkins_base_url.replace('https://', '');
if not groovy_url.endswith('/'):
groovy_url = '%s/' % groovy_url
return 'https://%s:%s@%sscriptText' % (
jenkins_auth_user,
jenkins_auth_password,
groovy_url)
def get_jenkins_ami_id():
groovy_url = get_groovy_url()
groovy_script = """
def foundAmi = ""
Jenkins.instance.clouds.each {
if (it.displayName == '%s') {
it.getTemplates().each {
if (it.getDisplayName().toLowerCase().contains("%s".toLowerCase())) {
// By definition, this will return the last result it finds
// You better make sure you supply a unique ami_profile_name ;)
foundAmi = it.getAmi();
}
}
}
}
println(foundAmi)
""" % (ec2_cloud_instance, ami_profile_name)
payload = {'script': groovy_script}
r = requests.post(groovy_url, verify=False, data=payload)
if not r.status_code == 200:
print 'HTTP POST to Jenkins URL %s resulted in %s' % (groovy_url, r.status_code)
print r.headers
print r.text
sys.exit(1)
return r.text.strip()
def update_jenkins_ami_id(ami_id):
groovy_url = get_groovy_url()
groovy_script = """
def foundAmi = ""
Jenkins.instance.clouds.each {
if (it.displayName == '%s') {
it.getTemplates().each {
if (it.getDisplayName().toLowerCase().contains("%s".toLowerCase())) {
// By definition, this will update all the results it finds
// You better make sure you supply a unique ami_profile_name ;)
it.setAmi("%s")
foundAmi = "yes"
}
}
}
}
Jenkins.instance.save()
println(foundAmi)
""" % (ec2_cloud_instance, ami_profile_name, ami_id)
payload = {'script': groovy_script}
r = requests.post(groovy_url, verify=False, data=payload)
if not r.status_code == 200:
print 'HTTP POST to Jenkins URL %s resulted in %s' % (groovy_url, r.status_code)
print r.headers
print r.text
sys.exit(1)
return r.text.strip() == "yes"
def main():
# Very high level overview of how this is supposed to work:
# ---------------------------------------------------------
# Get the Jenkins build output and check for errors
# - If there were errors,
# - delete the ami that was created
# - fail the build
# - If there were no errors,
# - Update Jenkins with the newly created AMI ID
# - Delete the old AMI in AWS
# - Pass the build
error_lines = get_error_lines(get_jenkins_build_output())
packer_ami_id = get_packer_ami_id(get_jenkins_build_output())
if error_lines:
print error_lines
print "Deleting newly created AMI %s" % packer_ami_id
delete_ami(packer_ami_id)
sys.exit(1)
old_jenkins_ami_id = get_jenkins_ami_id()
if not old_jenkins_ami_id:
print "Could not find (current) Jenkins AMI ID -- moving on"
update_success = update_jenkins_ami_id(packer_ami_id)
if not update_success:
print "Ran into an error when attempting to update the Jenkins AMI ID"
print "Deleting newly created AMI %s" % packer_ami_id
delete_ami(packer_ami_id)
sys.exit(1)
if old_jenkins_ami_id:
print "Deleting previous Jenkins AMI %s in AWS" % old_jenkins_ami_id
delete_ami(old_jenkins_ami_id)
if __name__ == '__main__':
main()
@marvinpinto

This comment has been minimized.

Copy link
Owner Author

@marvinpinto marvinpinto commented Jul 6, 2015

See jenkinsci/ec2-plugin#154 for more context on what this script is for!

@emoshaya

This comment has been minimized.

Copy link

@emoshaya emoshaya commented Aug 6, 2015

Hi there, I'm interested in using your script... Could you please explain what EC2_CLOUD_INSTANCE and AMI_PROFILE_NAME variables represent? Should I set AMI_PROFILE_NAME to the instance description in jenkins' global configuration page?

@emoshaya

This comment has been minimized.

Copy link

@emoshaya emoshaya commented Aug 6, 2015

ec2ami

@emoshaya

This comment has been minimized.

Copy link

@emoshaya emoshaya commented Aug 6, 2015

Is the description what I use for AMI_PROFILE_NAME?

@obscurerichard

This comment has been minimized.

Copy link

@obscurerichard obscurerichard commented Mar 8, 2018

@marvinpinto could you clarify what the copyright and license are for this bit of code? It would be great if this were explicitly MIT licensed for example.

@marvinpinto

This comment has been minimized.

Copy link
Owner Author

@marvinpinto marvinpinto commented Mar 8, 2018

@obscurerichard The MIT license sounds reasonable 👍

@emoshaya I haven't touched this in a very long time so I'm afraid I can't help you, sorry about that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.