Request buildapi to trigger a job for us.

    We return the request or None if dry_run is True.

    Raises BuildapiAuthError if credentials are invalid.

def trigger_arbitrary_job(repo_name, builder, revision, auth, files=None, dry_run=False,
                          extra_properties=None):
    """
    Request buildapi to trigger a job for us.

    We return the request or None if dry_run is True.

    Raises BuildapiAuthError if credentials are invalid.
    """
    assert len(revision) == 40, \
        'We do not accept revisions shorter than 40 chars'
    url = _builders_api_url(repo_name, builder, revision)
    payload = _payload(repo_name, revision, files, extra_properties)

    if dry_run:
        LOG.info("Dry-run: We were going to request a job for '{}'".format(builder))
        LOG.info("         with this payload: {}".format(str(payload)))
        LOG.info("         with these files: {}".format(files))
        return None

    # NOTE: A good response returns json with request_id as one of the keys
    req = requests.post(
        url,
        headers={'Accept': 'application/json'},
        data=payload,
        auth=auth,
        timeout=TCP_TIMEOUT,
    )
    if req.status_code == 401:
        raise BuildapiAuthError("Your credentials were invalid. Please try again.")
    elif req.status_code == 503:
        raise BuildapiDown("Please file a bug {}".format(url))

    try:
        req.json()
        return req

    except ValueError:
        LOG.info('repo: {}, builder: {}, revision: {}'.format(repo_name, builder, revision))
        LOG.error("We did not get info from %s (status code: %s)" % (url, req.status_code))
        return None