testing/docker/rust-build/tcbuild.py
author Sebastian Hengst <archaeopteryx@coole-files.de>
Mon, 19 Dec 2016 21:15:07 +0100
changeset 354459 9d0c5328c89782f32bb458487278944bf219a417
parent 354455 taskcluster/docker/rust-build/tcbuild.py@fe7303de56be76914f0eebe008b0ef02c803b6cd
child 354523 0f3603e365f029bc9ea5f926d32f6f0f8aa4d998
permissions -rw-r--r--
Backed out changeset fe7303de56be (bug 1302763) for failing webdriver tests on Linux x64 debug (geckodriver.manifest fetch failed). r=backout

#!/bin/env python
'''
This script triggers a taskcluster task, waits for it to finish,
fetches the artifacts, uploads them to tooltool, and updates
the in-tree tooltool manifests.
'''

from __future__ import print_function

import requests.packages.urllib3

import argparse
import datetime
import json
import os
import shutil
import sys
import taskcluster
import tempfile
import time
import tooltool

def local_file(filename):
    '''
    Return a path to a file next to this script.
    '''
    return os.path.join(os.path.dirname(__file__), filename)

def read_tc_auth(tc_auth_file):
    '''
    Read taskcluster credentials from tc_auth_file and return them as a dict.
    '''
    return json.load(open(tc_auth_file, 'rb'))

def fill_template_dict(d, keys):
    for key, val in d.items():
        if isinstance(val, basestring) and '{' in val:
            d[key] = val.format(**keys)
        elif isinstance(val, dict):
            fill_template_dict(val, keys)

def fill_template(template_file, keys):
    '''
    Take the file object template_file, parse it as JSON, and
    interpolate (using str.template) its keys using keys.
    '''
    template = json.load(template_file)
    fill_template_dict(template, keys)
    return template

def spawn_task(queue, args):
    '''
    Spawn a Taskcluster task in queue using args.
    '''
    task_id = taskcluster.utils.slugId()
    with open(local_file('task.json'), 'rb') as template:
        keys = vars(args)
        now = datetime.datetime.utcnow()
        keys['task_created'] = now.isoformat() + 'Z'
        keys['task_deadline'] = (now + datetime.timedelta(hours=2)).isoformat() + 'Z'
        keys['artifacts_expires'] = (now + datetime.timedelta(days=1)).isoformat() + 'Z'
        payload = fill_template(template, keys)
    queue.createTask(task_id, payload)
    print('--- %s task %s submitted ---' % (now, task_id))
    return task_id

def wait_for_task(queue, task_id, initial_wait=5):
    '''
    Wait until queue reports that task task_id is completed, and return
    its run id.

    Sleep for initial_wait seconds before checking status the first time.
    Then poll periodically and print a running log of the task status.
    '''
    time.sleep(initial_wait)
    previous_state = None
    have_ticks = False
    while True:
        res = queue.status(task_id)
        state = res['status']['state']
        if state != previous_state:
            now = datetime.datetime.utcnow()
            if have_ticks:
              sys.stdout.write('\n')
              have_ticks = False
            print('--- %s task %s %s ---' % (now, task_id, state))
            previous_state = state
        if state == 'completed':
            return len(res['status']['runs']) - 1
        if state in ('failed', 'exception'):
            raise Exception('Task failed')
        sys.stdout.write('.')
        sys.stdout.flush()
        have_ticks = True
        time.sleep(10)

def fetch_artifact(queue, task_id, run_id, name, dest_dir):
    '''
    Fetch the artifact with name from task_id and run_id in queue,
    write it to a file in dest_dir, and return the path to the written
    file.
    '''
    url = queue.buildUrl('getArtifact', task_id, run_id, name)
    fn = os.path.join(dest_dir, os.path.basename(name))
    print('Fetching %s...' % name)
    try:
        r = requests.get(url, stream=True)
        r.raise_for_status()
        with open(fn, 'wb') as f:
            for chunk in r.iter_content(1024):
                f.write(chunk)
    except requests.exceptions.HTTPError:
        print('HTTP Error %d fetching %s' % (r.status_code, name))
        return None
    return fn

def make_artifact_dir(task_id, run_id):
    prefix = 'tc-artifacts.%s.%d.' % (task_id, run_id)
    print('making artifact dir %s' % prefix)
    return tempfile.mkdtemp(prefix=prefix)

def fetch_artifacts(queue, task_id, run_id):
    '''
    Fetch all artifacts from task_id and run_id in queue, write them to
    temporary files, and yield the path to each.
    '''
    try:
        tempdir = make_artifact_dir(task_id, run_id)
        res = queue.listArtifacts(task_id, run_id)
        for a in res['artifacts']:
            # Skip logs
            if a['name'].startswith('public/logs'):
                continue
            # Skip interfaces
            if a['name'].startswith('private/docker-worker'):
                continue
            yield fetch_artifact(queue, task_id, run_id, a['name'], tempdir)
    finally:
        if os.path.isdir(tempdir):
            #shutil.rmtree(tempdir)
            print('Artifacts downloaded to %s' % tempdir)
            pass

def upload_to_tooltool(tooltool_auth, task_id, artifact):
    '''
    Upload artifact to tooltool using tooltool_auth as the authentication token.
    Return the path to the generated tooltool manifest.
    '''
    try:
        oldcwd = os.getcwd()
        os.chdir(os.path.dirname(artifact))
        manifest = artifact + '.manifest'
        tooltool.main([
            'tooltool.py',
            'add',
            '--visibility=public',
            '-m', manifest,
            artifact
        ])
        tooltool.main([
            'tooltool.py',
            'upload',
            '-m', manifest,
            '--authentication-file', tooltool_auth,
            '--message', 'Built from taskcluster task {}'.format(task_id),
        ])
        return manifest
    finally:
        os.chdir(oldcwd)

def update_manifest(artifact, manifest, local_gecko_clone):
    platform = 'linux'
    manifest_dir = os.path.join(local_gecko_clone,
                                'testing', 'config', 'tooltool-manifests')
    platform_dir = [p for p in os.listdir(manifest_dir)
                    if p.startswith(platform)][0]
    tree_manifest = os.path.join(manifest_dir, platform_dir, 'releng.manifest')
    print('%s -> %s' % (manifest, tree_manifest))
    shutil.copyfile(manifest, tree_manifest)

def main():
    parser = argparse.ArgumentParser(description='Build and upload binaries')
    parser.add_argument('taskcluster_auth', help='Path to a file containing Taskcluster client ID and authentication token as a JSON file in the form {"clientId": "...", "accessToken": "..."}')
    parser.add_argument('--tooltool-auth', help='Path to a file containing a tooltool authentication token valid for uploading files')
    parser.add_argument('--local-gecko-clone', help='Path to a local Gecko clone whose tooltool manifests will be updated with the newly-built binaries')
    parser.add_argument('--rust-branch', default='stable',
                        help='Revision of the rust repository to use')
    parser.add_argument('--task', help='Use an existing task')

    args = parser.parse_args()
    tc_auth = read_tc_auth(args.taskcluster_auth)
    queue = taskcluster.Queue({'credentials': tc_auth})
    if args.task:
        task_id, initial_wait = args.task, 0
    else:
        task_id, initial_wait = spawn_task(queue, args), 25
    run_id = wait_for_task(queue, task_id, initial_wait)
    for artifact in fetch_artifacts(queue, task_id, run_id):
        if args.tooltool_auth:
            manifest = upload_to_tooltool(args.tooltool_auth, task_id, artifact)
        if args.local_gecko_clone:
            update_manifest(artifact, manifest, args.local_gecko_clone)

if __name__ == '__main__':
    main()