def api_v1_org(org): """Organization overview. Returns a list of repositories that have been run through the system, with their most recent created date and run number. :param org: The GitHub username or organization (e.g. 'puppetlabs') :return: JSON containing an organization's repositories known to the system. """ response = es.search(q="source:{} AND payload.repository.owner.name:{}".format('jenkins-hookshot', org)) repos = [] for hit in response['hits']['hits']: repos.append(hit['_source']['payload']['repository']['full_name']) result = [] for repo in set(repos): last_run_uuid = redis.lindex(repo, -1).decode('utf-8') last_run_num = get_run_number(repo, last_run_uuid) resp = es.search(q="source:{} AND id:{}".format('jenkins-hookshot', last_run_uuid)) hit = resp['hits']['hits'][0]['_source'] repo = hit['repo'] last_run_timestamp = hit['@timestamp'] result.append(dict( name=repo, last_run_uuid=last_run_uuid, last_run_number=last_run_num, last_run_timestamp=last_run_timestamp, )) return jsonify(dict(org=org, repos=sorted(result, key=itemgetter('name'))))
def api_v1_repo(org, repo): """Repo overview. For the given repository (e.g. 'puppetlabs/puppet'), return a list of runs. :param org: GitHub username or organization (e.g. 'puppetlabs') :param repo: GitHub repository name (e.g. 'puppet') :return: JSON containing runs known to the system. """ # Note: here we're using the fullname of the repo, e.g. 'puppetlabs/puppet', # as is GitHub's convention. However, Flask sees the '/' character as a path # separator, so it has to be two individual arguments that we later combine. repo_full_name = '/'.join([org, repo]) result = [] response = es.search(q="source:{} AND payload.repository.full_name:\"{}\"".format('jenkins-hookshot', repo_full_name)) # TODO: limit the output of this endpoint to the last N runs for hit in response['hits']['hits']: hit = hit['_source'] uuid = hit['id'] number = get_run_number(repo_full_name, uuid) timestamp = hit['@timestamp'] head_sha = hit['payload']['head_commit']['id'] ref = hit['payload']['ref'] result.append(dict( run_uuid=uuid, run_number=number, timestamp=timestamp, head_sha=head_sha, ref=ref, )) return jsonify(repo=repo_full_name, runs=sorted(result, key=itemgetter('run_number'), reverse=True))
def api_v1_recent(): """Query Redis and Elasticsearch and return some basic info on recent runs. :return: JSON containing recent run info. """ result = [] # Query Redis for all the UUIDs stored in the 'recent' list # TODO: limit the output of this endpoint to the last N runs # TODO: Alternately, set a TTL on the items in the 'recent' list in Redis for run_uuid in redis.lrange('recent', 0, -1): run_uuid = run_uuid.decode('utf-8') response = es.search(q="source:{} AND id:{}".format('jenkins-hookshot', run_uuid), size=1) hook_payload = response['hits']['hits'][0]['_source'] repo = hook_payload['payload']['repository']['full_name'] result.append(dict( run_uuid=run_uuid, timestamp=hook_payload['@timestamp'], org=repo.split('/')[0], repo=repo.split('/')[1], head_sha=hook_payload['payload']['head_commit']['id'], run_number=get_run_number(repo, run_uuid), ref=hook_payload['payload']['ref'] )) return jsonify(runs=result)
def api_v1_run_job_log(org, repo, run_number, job_name): """Return a plaintext (raw) console log for a particular job. :param org: GitHub organization or username (e.g. 'puppetlabs') :param repo: GitHub repository name (e.g. 'puppet') :param run_number: A specific build number (integer) :param job_name: Job name (string) :return: JSON containing job info """ # The Jenkins Logstash plugin creates a new item in an array for each line # of the Jenkins console log output. Because it takes so long for Javascript # in the user's browser to try and join the array, we do it here. run_uuid = get_run_uuid('/'.join([org, repo]), run_number) job_resp = es.search(q="source:{} AND data.rootProjectName:{}".format( 'jenkins', '__'.join([run_uuid, job_name])), _source_include="message", size=1) log = '\n'.join(job_resp['hits']['hits'][0]['_source']['message']) return log, 200, { 'Content-Type': 'text/plain; charset=utf-8' }
def api_v1_run_job(org, repo, run_number, job_name): """Return info about a particular job in a run, excluding the console log. :param org: GitHub organization or username (e.g. 'puppetlabs') :param repo: GitHub repository name (e.g. 'puppet') :param run_number: A specific build number (integer) :param job_name: Job name (string) :return: JSON containing job info """ run_uuid = get_run_uuid('/'.join([org, repo]), run_number) job_resp = es.search(q="source:{} AND data.rootProjectName:{}".format( 'jenkins', '__'.join([run_uuid, job_name])), _source_exclude="message", size=1) job = job_resp['hits']['hits'][0]['_source']['data'] if 'testResults' in job: test_results = dict( total=job['testResults']['totalCount'], skipped=job['testResults']['skipCount'], failed=job['testResults']['failCount'], passed=job['testResults']['totalCount'] - job['testResults']['skipCount'] - job['testResults']['failCount'], tests=dict( failed=job['testResults']['failedTests'], ), ) else: test_results = {} return jsonify(dict( org=org, repo=repo, run_number=run_number, run_uuid=run_uuid, name=job_name, timestamp=job['timestamp'], duration=millis_to_secs(job['buildDuration']), status=job['result'].lower(), test_results=test_results, ))
def api_v1_run(org, repo, run_number): """Given a specific repo and run number, return info on the run and the jobs in the run. :param org: GitHub organization or username (e.g. 'puppetlabs') :param repo: GitHub repository name (e.g. 'puppet') :param run_number: A specific build number (integer) :return: JSON containing run info """ run_uuid = get_run_uuid('/'.join([org, repo]), run_number) hook_resp = es.search(q="source:{} AND id:{}".format('jenkins-hookshot', run_uuid), size=1) hook_payload = hook_resp['hits']['hits'][0]['_source']['payload'] commits = [] for commit in hook_payload['commits']: commits.append(dict( author=dict( email=commit['author']['email'], name=commit['author']['name'], github_username=commit['author']['username'], ), comitter=dict( email=commit['committer']['email'], name=commit['committer']['name'], github_username=commit['committer']['username'], ), sha=commit['id'], subject=commit['message'].split('\n')[0], message='\n'.join(commit['message'].split('\n')[2::]), timestamp=commit['timestamp'], url=commit['url'], )) # Get job data, excluding the console log. Limit jobs to 500. job_resp = es.search(q="source:{} AND data.rootProjectName:{}".format( 'jenkins', run_uuid), _source_exclude="message", size=500) jobs = [] for hit in job_resp['hits']['hits']: hit = hit['_source']['data'] job = {} # As mentioned in the "caveats" section of the README, we assume that # jobs are named using '__' as a separator between the UUID and whatever # arbitrary job name the user decides to create. As such, '__' should # *never* be used after the UUID. # # I tried experimenting with different separators, but the separator # needs to be compatible with the URLs jenkins-hookshot generates to # POST the seed job config to Jenkins. Double underscore seemed sane. # -- roger, 2015-02-12 # job['name'] = hit['rootProjectName'].split('__')[-1] job['timestamp'] = hit['timestamp'] job['duration'] = millis_to_secs(hit['buildDuration']) job['status'] = hit['result'].lower() job['build_variables'] = hit['buildVariables'] job['cell'] = hit['projectName'] jobs.append(job) return jsonify(dict( org=org, repo=repo, repo_description=hook_payload['repository']['description'], repo_url=hook_payload['repository']['url'], org_url=hook_payload['sender']['html_url'], head_sha=hook_payload['head_commit']['id'], ref=hook_payload['ref'], commits=commits, run_number=run_number, run_uuid=run_uuid, jobs=jobs, ))