Skip to content

Instantly share code, notes, and snippets.

@lazykyama
Last active August 29, 2015 14:17
Show Gist options
  • Save lazykyama/f1bcfff724eebae59ca7 to your computer and use it in GitHub Desktop.
Save lazykyama/f1bcfff724eebae59ca7 to your computer and use it in GitHub Desktop.
リモートサーバのJenkinsジョブと連携するためのスクリプト。RemoteTriggerPluginで対応できないケース用。ビルド完了を待機し、ビルド結果のURLと成否を出力します。Synchronized build script for remote jenkins job.
#!/usr/bin/env python
# -*- coding: utf-8-unix -*-
"""build remote jenkins job and wait it until finish to build.
Usage:
syncbuild_remote_jenkins.py --job JOB [--host HOST] [--port PORT] [--base-path PATH]
[--params PARAMS] [--interval INTERVAL]
syncbuild_remote_jenkins.py -h|--help
Options:
--job JOB target jenkins job name.
--host HOST jenkins hostname [default: localhost].
--port PORT jenkins port number [default: 8080].
--base-path PATH jenkins base path [default: /job].
--params PARAMS parameter used by remote job in JSON format.
--interval INTERVAL interval second to poll remote job state [default: 5].
-h --help show this help message and exit
-v --verbose set logging level to debug.
"""
"""
:auther: kyama http://sukimaprogrammer.blogspot.jp/
:since: 0.0.1
:version: 0.0.4
This source is distributed under the MIT License.
Copyright (c) 2015 lazykyama http://sukimaprogrammer.blogspot.jp/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""
__version__='0.0.4'
__author__='lazykyama'
__license__='MIT License'
__copyright__='Copyright (c) 2015 lazykyama'
import argparse
import json
import logging
import sys
import time
import urllib2
import urllib
DEFAULT_QUEUE_POLLING_INTERVAL_SEC = 30
def __validate_args(args):
def validate_error(param, validator, err_msg, converter=lambda p: p):
if not validator(param):
sys.stderr.write('[ERROR] {}.\n'.format(err_msg))
raise RuntimeError('fail to validate parameter.')
return converter(param)
validators = {
'host': lambda p: validate_error(p, lambda p: len(p) > 0,
'invalid hostname: {}'.format(p)),
'port': lambda p: validate_error(p, lambda p: 0 < p < 65536,
'invalid portnum: {}'.format(p)),
'params': lambda p: validate_error(p, lambda p: p is not None and len(p) > 0,
'invalid parmaeter: {}'.format(p),
converter = lambda p: json.loads(p)),
'interval': lambda p: validate_error(p, lambda p: p > 0,
'invalid interval second: {}'.format(p))
}
args.host = validators['host'](args.host)
args.port = validators['port'](args.port)
args.params = validators['params'](args.params)
args.interval = validators['interval'](args.interval)
return args
def __parse_args():
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 8080
DEFAULT_BASE_PATH = '/job'
STATEPOLLING_INTERVAL_SECOND = 5
parser = argparse.ArgumentParser(
description='kick and wait remote jenkins job until finishing it.')
parser.add_argument('--host', type=str, default=DEFAULT_HOST,
help='jenkins hostname [default: {}].'.format(DEFAULT_HOST))
parser.add_argument('--port', type=int, default=DEFAULT_PORT,
help='jenkins port number [default: {}].'.format(DEFAULT_PORT))
parser.add_argument('--base-path', type=str, default=DEFAULT_BASE_PATH,
help='jenkins base path [default: {}].'.format(DEFAULT_BASE_PATH))
parser.add_argument('--job', type=str, required=True,
help='target jenkins job name.')
parser.add_argument('--params', type=str, default='{}',
help='parameter used by remote job in JSON format.')
parser.add_argument('--interval', type=int, default=STATEPOLLING_INTERVAL_SECOND,
help='interval second to poll remote job state [default: {}].'.format(
STATEPOLLING_INTERVAL_SECOND))
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", dest="verbose",
default=False, action="store_true",
help="set logging level to debug.")
args = __validate_args(parser.parse_args())
if args.verbose:
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] [%(filename)s:%(lineno)d] %(message)s')
else:
logging.basicConfig(level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s')
logging.debug(args)
return args
def __fetch_remote_state(url):
logging.debug('send request to get remote status: {}.'.format(url))
response = urllib2.urlopen(url)
if response.code != 200:
raise RuntimeError(
'unexpected status: {}'.format(response.code))
return json.load(response)
def __build_remote_job(args):
kick_remote_job_path = 'http://{}:{}{}/{}/buildWithParameters'.format(
args.host, args.port, args.base_path, args.job)
params = urllib.urlencode(args.params)
kick_remote_job_url = '{}?{}'.format(kick_remote_job_path, params)
logging.debug('send request to create job: {}.'.format(kick_remote_job_url))
response = urllib2.urlopen(kick_remote_job_url)
if response.code != 201:
raise RuntimeError(
'unexpected status in building job: {}'.format(response.code))
location = response.info().getheader('Location')
if location is None:
raise RuntimeError(
'unexpected response header without Location.')
queue_state_url = '{}api/json'.format(location)
build_url = None
while build_url is None:
queue_state = __fetch_remote_state(queue_state_url)
executable_info = 'executable' in queue_state and queue_state['executable']
if not executable_info:
logging.debug('wait to release from queue.')
time.sleep(DEFAULT_QUEUE_POLLING_INTERVAL_SEC)
continue
build_url = executable_info['url']
return build_url
def __wait_to_finish_remote_job(args, build_url):
job_state_url = '{}api/json'.format(build_url)
state = None
while state is None:
response = __fetch_remote_state(job_state_url)
result = 'result' in response and response['result']
if not result:
logging.debug('wait to finish building job.')
time.sleep(args.interval)
continue
state = result
return state
def main():
args = __parse_args()
try:
print('build remote job[{}].'.format(args.job))
build_url = __build_remote_job(args)
state = __wait_to_finish_remote_job(args, build_url)
except RuntimeError as e:
logging.error('fail to build remote job: {}'.format(e))
return 1
print('buliding remote job[{}] is {}.'.format(args.job, state))
print('build result: {}'.format(build_url))
if state != 'SUCCESS':
return 1
return 0
if __name__ == '__main__':
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment