Backed out 4 changesets (bug 1508381) for multiple Windows build bustages CLOSED TREE

Backed out changeset f01cec6f712e (bug 1508381)
Backed out changeset ba69e59924de (bug 1508381)
Backed out changeset 97fe4e5a665e (bug 1508381)
Backed out changeset 0c3065c12bef (bug 1508381)
This commit is contained in:
arthur.iakab 2019-01-31 23:14:11 +02:00
Родитель 0e57cd7404
Коммит c152ccec1d
57 изменённых файлов: 595 добавлений и 842 удалений

Просмотреть файл

@ -10,8 +10,6 @@ import os
import re import re
import requests import requests
from taskcluster.notify import Notify from taskcluster.notify import Notify
from taskcluster import optionsFromEnvironment
from taskgraph.util.taskcluster import get_root_url
from operator import itemgetter from operator import itemgetter
from mozilla_version.gecko import GeckoVersion from mozilla_version.gecko import GeckoVersion
@ -218,9 +216,11 @@ Task group: [{task_group_id}](https://tools.taskcluster.net/groups/{task_group_i
subject = '{} Build of {} {} build {}'.format(subject_prefix, product, version, build_number) subject = '{} Build of {} {} build {}'.format(subject_prefix, product, version, build_number)
notify_options = optionsFromEnvironment({'rootUrl': get_root_url()}) notify_options = {}
if 'TASKCLUSTER_PROXY_URL' in os.environ: if 'TASKCLUSTER_PROXY_URL' in os.environ:
notify_options['rootUrl'] = os.environ['TASKCLUSTER_PROXY_URL'] # Until bug 1460015 is finished, use the old baseUrl style of proxy URL
base_url = os.environ['TASKCLUSTER_PROXY_URL'].rstrip('/')
notify_options['baseUrl'] = '{}/notify/v1'.format(base_url)
notify = Notify(notify_options) notify = Notify(notify_options)
for address in addresses: for address in addresses:
notify.email({ notify.email({

Просмотреть файл

@ -33,7 +33,8 @@ then
test "${AWS_BUCKET_NAME}" test "${AWS_BUCKET_NAME}"
set +x # Don't echo these. set +x # Don't echo these.
secret_url="${TASKCLUSTER_PROXY_URL}/api/auth/v1/aws/s3/read-write/${AWS_BUCKET_NAME}/${S3_PATH}" # Until bug 1460015 is finished, use baseUrl-style proxy URLs
secret_url="${TASKCLUSTER_PROXY_URL}/auth/v1/aws/s3/read-write/${AWS_BUCKET_NAME}/${S3_PATH}"
AUTH=$(curl "${secret_url}") AUTH=$(curl "${secret_url}")
AWS_ACCESS_KEY_ID=$(echo "${AUTH}" | jq -r '.credentials.accessKeyId') AWS_ACCESS_KEY_ID=$(echo "${AUTH}" | jq -r '.credentials.accessKeyId')
AWS_SECRET_ACCESS_KEY=$(echo "${AUTH}" | jq -r '.credentials.secretAccessKey') AWS_SECRET_ACCESS_KEY=$(echo "${AUTH}" | jq -r '.credentials.secretAccessKey')

Просмотреть файл

@ -68,7 +68,8 @@ fi
if [ -n "${ARC_SECRET}" ] && getent hosts taskcluster if [ -n "${ARC_SECRET}" ] && getent hosts taskcluster
then then
set +x # Don't echo these set +x # Don't echo these
secrets_url="${TASKCLUSTER_PROXY_URL}/api/secrets/v1/secret/${ARC_SECRET}" # Until bug 1460015 is finished, use baseUrl-style proxy URLs
secrets_url="${TASKCLUSTER_PROXY_URL}/secrets/v1/secret/${ARC_SECRET}"
SECRET=$(curl "${secrets_url}") SECRET=$(curl "${secrets_url}")
TOKEN=$(echo "${SECRET}" | jq -r '.secret.token') TOKEN=$(echo "${SECRET}" | jq -r '.secret.token')
elif [ -n "${ARC_TOKEN}" ] # Allow for local testing. elif [ -n "${ARC_TOKEN}" ] # Allow for local testing.

Просмотреть файл

@ -36,7 +36,8 @@ fi
if [ -n "${ARC_SECRET}" ] && getent hosts taskcluster if [ -n "${ARC_SECRET}" ] && getent hosts taskcluster
then then
set +x # Don't echo these set +x # Don't echo these
secrets_url="${TASKCLUSTER_PROXY_URL}/api/secrets/v1/secret/${ARC_SECRET}" # Until bug 1460015 is finished, use the old, baseUrl-style proxy URLs
secrets_url="${TASKCLUSTER_PROXY_URL}/secrets/v1/secret/${ARC_SECRET}"
SECRET=$(curl "${secrets_url}") SECRET=$(curl "${secrets_url}")
TOKEN=$(echo "${SECRET}" | jq -r '.secret.token') TOKEN=$(echo "${SECRET}" | jq -r '.secret.token')
elif [ -n "${ARC_TOKEN}" ] # Allow for local testing. elif [ -n "${ARC_TOKEN}" ] # Allow for local testing.

Просмотреть файл

@ -396,7 +396,8 @@ def command_task_artifacts(args):
task=fetch['task'], artifact=fetch['artifact']) task=fetch['task'], artifact=fetch['artifact'])
url = api(root_url, 'queue', 'v1', path) url = api(root_url, 'queue', 'v1', path)
else: else:
url = ('{proxy_url}/api/queue/v1/task/{task}/artifacts/{artifact}').format( # Until bug 1460015 is finished, use the old baseUrl style proxy URLs
url = ('{proxy_url}/queue/v1/task/{task}/artifacts/{artifact}').format(
proxy_url=os.environ['TASKCLUSTER_PROXY_URL'], proxy_url=os.environ['TASKCLUSTER_PROXY_URL'],
task=fetch['task'], task=fetch['task'],
artifact=fetch['artifact']) artifact=fetch['artifact'])

Просмотреть файл

@ -21,6 +21,7 @@ from taskgraph.util.attributes import TRUNK_PROJECTS
from taskgraph.util.hash import hash_path from taskgraph.util.hash import hash_path
from taskgraph.util.treeherder import split_symbol from taskgraph.util.treeherder import split_symbol
from taskgraph.transforms.base import TransformSequence from taskgraph.transforms.base import TransformSequence
from taskgraph.util.taskcluster import get_root_url
from taskgraph.util.schema import ( from taskgraph.util.schema import (
validate_schema, validate_schema,
Schema, Schema,
@ -492,6 +493,11 @@ def build_docker_worker_payload(config, task, task_def):
else: else:
raise Exception("unknown docker image type") raise Exception("unknown docker image type")
# propagate our TASKCLUSTER_ROOT_URL to the task; note that this will soon
# be provided directly by the worker, making this redundant:
# https://bugzilla.mozilla.org/show_bug.cgi?id=1460015
worker['env']['TASKCLUSTER_ROOT_URL'] = get_root_url()
features = {} features = {}
if worker.get('relengapi-proxy'): if worker.get('relengapi-proxy'):
@ -499,6 +505,7 @@ def build_docker_worker_payload(config, task, task_def):
if worker.get('taskcluster-proxy'): if worker.get('taskcluster-proxy'):
features['taskclusterProxy'] = True features['taskclusterProxy'] = True
worker['env']['TASKCLUSTER_PROXY_URL'] = 'http://taskcluster/'
if worker.get('allow-ptrace'): if worker.get('allow-ptrace'):
features['allowPtrace'] = True features['allowPtrace'] = True
@ -523,6 +530,11 @@ def build_docker_worker_payload(config, task, task_def):
else: else:
worker['env']['SCCACHE_DISABLE'] = '1' worker['env']['SCCACHE_DISABLE'] = '1'
# this will soon be provided directly by the worker:
# https://bugzilla.mozilla.org/show_bug.cgi?id=1460015
if features.get('taskclusterProxy'):
worker['env']['TASKCLUSTER_PROXY_URL'] = 'http://taskcluster'
capabilities = {} capabilities = {}
for lo in 'audio', 'video': for lo in 'audio', 'video':
@ -755,6 +767,11 @@ def build_generic_worker_payload(config, task, task_def):
env = worker.get('env', {}) env = worker.get('env', {})
# propagate our TASKCLUSTER_ROOT_URL to the task; note that this will soon
# be provided directly by the worker, making this redundant:
# https://bugzilla.mozilla.org/show_bug.cgi?id=1460015
env['TASKCLUSTER_ROOT_URL'] = get_root_url()
if task.get('needs-sccache'): if task.get('needs-sccache'):
env['USE_SCCACHE'] = '1' env['USE_SCCACHE'] = '1'
# Disable sccache idle shutdown. # Disable sccache idle shutdown.
@ -809,6 +826,9 @@ def build_generic_worker_payload(config, task, task_def):
if worker.get('taskcluster-proxy'): if worker.get('taskcluster-proxy'):
features['taskclusterProxy'] = True features['taskclusterProxy'] = True
# this will soon be provided directly by the worker:
# https://bugzilla.mozilla.org/show_bug.cgi?id=1460015
worker['env']['TASKCLUSTER_PROXY_URL'] = 'http://taskcluster'
if worker.get('run-as-administrator', False): if worker.get('run-as-administrator', False):
features['runAsAdministrator'] = True features['runAsAdministrator'] = True
@ -1307,6 +1327,10 @@ def build_always_optimized_payload(config, task, task_def):
def build_macosx_engine_payload(config, task, task_def): def build_macosx_engine_payload(config, task, task_def):
worker = task['worker'] worker = task['worker']
# propagate our TASKCLUSTER_ROOT_URL to the task; note that this will soon
# be provided directly by the worker, making this redundant
worker.setdefault('env', {})['TASKCLUSTER_ROOT_URL'] = get_root_url()
artifacts = map(lambda artifact: { artifacts = map(lambda artifact: {
'name': artifact['name'], 'name': artifact['name'],
'path': artifact['path'], 'path': artifact['path'],

Просмотреть файл

@ -31,23 +31,10 @@ CONCURRENCY = 50
@memoize @memoize
def get_root_url(use_proxy=False): def get_root_url():
"""Get the current TASKCLUSTER_ROOT_URL. When running in a task, this must """Get the current TASKCLUSTER_ROOT_URL. When running in a task, this must
come from $TASKCLUSTER_ROOT_URL; when run on the command line, we apply a come from $TASKCLUSTER_ROOT_URL; when run on the command line, we apply a
defualt that points to the production deployment of Taskcluster. If use_proxy defualt that points to the production deployment of Taskcluster."""
is set, this attempts to get TASKCLUSTER_PROXY_URL instead, failing if it
is not set."""
if use_proxy:
try:
return os.environ['TASKCLUSTER_PROXY_URL']
except KeyError:
if 'TASK_ID' not in os.environ:
raise RuntimeError(
'taskcluster-proxy is not available when not executing in a task')
else:
raise RuntimeError(
'taskcluster-proxy is not enabled for this task')
if 'TASKCLUSTER_ROOT_URL' not in os.environ: if 'TASKCLUSTER_ROOT_URL' not in os.environ:
if 'TASK_ID' in os.environ: if 'TASK_ID' in os.environ:
raise RuntimeError('$TASKCLUSTER_ROOT_URL must be set when running in a task') raise RuntimeError('$TASKCLUSTER_ROOT_URL must be set when running in a task')
@ -154,7 +141,11 @@ def get_artifact_path(task, path):
def get_index_url(index_path, use_proxy=False, multiple=False): def get_index_url(index_path, use_proxy=False, multiple=False):
index_tmpl = liburls.api(get_root_url(use_proxy), 'index', 'v1', 'task{}/{}') if use_proxy:
# Until bug 1460015 is finished, use the old baseUrl style of proxy URL
index_tmpl = os.environ['TASKCLUSTER_PROXY_URL'] + '/index/v1/task{}/{}'
else:
index_tmpl = liburls.api(get_root_url(), 'index', 'v1', 'task{}/{}')
return index_tmpl.format('s' if multiple else '', index_path) return index_tmpl.format('s' if multiple else '', index_path)
@ -204,7 +195,11 @@ def parse_time(timestamp):
def get_task_url(task_id, use_proxy=False): def get_task_url(task_id, use_proxy=False):
task_tmpl = liburls.api(get_root_url(use_proxy), 'queue', 'v1', 'task/{}') if use_proxy:
# Until bug 1460015 is finished, use the old baseUrl style of proxy URL
task_tmpl = os.environ['TASKCLUSTER_PROXY_URL'] + '/queue/v1/task/{}'
else:
task_tmpl = liburls.api(get_root_url(), 'queue', 'v1', 'task/{}')
return task_tmpl.format(task_id) return task_tmpl.format(task_id)
@ -245,13 +240,17 @@ def rerun_task(task_id):
def get_current_scopes(): def get_current_scopes():
"""Get the current scopes. This only makes sense in a task with the Taskcluster """Get the current scopes. This only makes sense in a task with the Taskcluster
proxy enabled, where it returns the actual scopes accorded to the task.""" proxy enabled, where it returns the actual scopes accorded to the task."""
auth_url = liburls.api(get_root_url(use_proxy=True), 'auth', 'v1', 'scopes/current') # Until bug 1460015 is finished, use the old baseUrl style of proxy URL
resp = _do_request(auth_url) resp = _do_request(os.environ['TASKCLUSTER_PROXY_URL'] + '/auth/v1/scopes/current')
return resp.json().get("scopes", []) return resp.json().get("scopes", [])
def get_purge_cache_url(provisioner_id, worker_type, use_proxy=False): def get_purge_cache_url(provisioner_id, worker_type, use_proxy=False):
url_tmpl = liburls.api(get_root_url(use_proxy), 'purge-cache', 'v1', 'purge-cache/{}/{}') if use_proxy:
# Until bug 1460015 is finished, use the old baseUrl style of proxy URL
url_tmpl = os.environ['TASKCLUSTER_PROXY_URL'] + '/purge-cache/v1/purge-cache/{}/{}'
else:
url_tmpl = liburls.api(get_root_url(), 'purge-cache', 'v1', 'purge-cache/{}/{}')
return url_tmpl.format(provisioner_id, worker_type) return url_tmpl.format(provisioner_id, worker_type)
@ -268,7 +267,11 @@ def purge_cache(provisioner_id, worker_type, cache_name, use_proxy=False):
def send_email(address, subject, content, link, use_proxy=False): def send_email(address, subject, content, link, use_proxy=False):
"""Sends an email using the notify service""" """Sends an email using the notify service"""
logger.info('Sending email to {}.'.format(address)) logger.info('Sending email to {}.'.format(address))
url = liburls.api(get_root_url(use_proxy), 'notify', 'v1', 'email') if use_proxy:
# Until bug 1460015 is finished, use the old baseUrl style of proxy URL
url = os.environ['TASKCLUSTER_PROXY_URL'] + '/notify/v1/email'
else:
url = liburls.api(get_root_url(), 'notify', 'v1', 'email')
_do_request(url, json={ _do_request(url, json={
'address': address, 'address': address,
'subject': subject, 'subject': subject,

2
third_party/python/requirements.in поставляемый
Просмотреть файл

@ -12,7 +12,7 @@ python-hglib==2.4
redo==2.0.2 redo==2.0.2
requests==2.9.1 requests==2.9.1
six==1.10.0 six==1.10.0
taskcluster==6.0.0 taskcluster==4.0.1
taskcluster-urls==11.0.0 taskcluster-urls==11.0.0
virtualenv==15.2.0 virtualenv==15.2.0
voluptuous==0.11.5 voluptuous==0.11.5

8
third_party/python/requirements.txt поставляемый
Просмотреть файл

@ -104,10 +104,10 @@ taskcluster-urls==11.0.0 \
--hash=sha256:18dcaa9c2412d34ff6c78faca33f0dd8f2384e3f00a98d5832c62d6d664741f0 \ --hash=sha256:18dcaa9c2412d34ff6c78faca33f0dd8f2384e3f00a98d5832c62d6d664741f0 \
--hash=sha256:2aceab7cf5b1948bc197f2e5e50c371aa48181ccd490b8bada00f1e3baf0c5cc \ --hash=sha256:2aceab7cf5b1948bc197f2e5e50c371aa48181ccd490b8bada00f1e3baf0c5cc \
--hash=sha256:74bd2110b5daaebcec5e1d287bf137b61cb8cf6b2d8f5f2b74183e32bc4e7c87 --hash=sha256:74bd2110b5daaebcec5e1d287bf137b61cb8cf6b2d8f5f2b74183e32bc4e7c87
taskcluster==6.0.0 \ taskcluster==4.0.1 \
--hash=sha256:48ecd4898c7928deddfb34cb1cfe2b2505c68416e6c503f8a7f3dd0572425e96 \ --hash=sha256:27256511044346ac71a495d3c636f2add95c102b9b09f90d6fb1ea3e9949d311 \
--hash=sha256:6d5cf7bdbc09dc48b2d376b418b95c1c157a2d359c4b6b231c1fb14a323c0cc5 \ --hash=sha256:99dd90bc1c566968868c8b07ede32f8e031cbccd52c7195a61e802679d461447 \
--hash=sha256:e409fce7a72808e4f87dc7baca7a79d8b64d5c5045264b9e197c120cc40e219b --hash=sha256:d0360063c1a3fcaaa514bb31c03954ba573d2b671df40a2ecfdfd9339cc8e93e
virtualenv-clone==0.3.0 \ virtualenv-clone==0.3.0 \
--hash=sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7 \ --hash=sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7 \
--hash=sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8 \ --hash=sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8 \

2
third_party/python/taskcluster/PKG-INFO поставляемый
Просмотреть файл

@ -1,6 +1,6 @@
Metadata-Version: 1.1 Metadata-Version: 1.1
Name: taskcluster Name: taskcluster
Version: 6.0.0 Version: 4.0.1
Summary: Python client for Taskcluster Summary: Python client for Taskcluster
Home-page: https://github.com/taskcluster/taskcluster-client.py Home-page: https://github.com/taskcluster/taskcluster-client.py
Author: John Ford Author: John Ford

243
third_party/python/taskcluster/README.md поставляемый
Просмотреть файл

@ -70,7 +70,7 @@ python2-compatible and operate synchronously.
The objects under `taskcluster.aio` (e.g., `taskcluster.aio.Queue`) require The objects under `taskcluster.aio` (e.g., `taskcluster.aio.Queue`) require
`python>=3.6`. The async objects use asyncio coroutines for concurrency; this `python>=3.5`. The async objects use asyncio coroutines for concurrency; this
allows us to put I/O operations in the background, so operations that require allows us to put I/O operations in the background, so operations that require
the cpu can happen sooner. Given dozens of operations that can run concurrently the cpu can happen sooner. Given dozens of operations that can run concurrently
(e.g., cancelling a medium-to-large task graph), this can result in significant (e.g., cancelling a medium-to-large task graph), this can result in significant
@ -101,10 +101,7 @@ Here's a slide deck for an [introduction to async python](https://gitpitch.com/e
```python ```python
import taskcluster import taskcluster
index = taskcluster.Index({ index = taskcluster.Index({'credentials': {'clientId': 'id', 'accessToken': 'accessToken'}})
'rootUrl': 'https://tc.example.com',
'credentials': {'clientId': 'id', 'accessToken': 'accessToken'},
})
index.ping() index.ping()
``` ```
@ -122,34 +119,18 @@ Here's a slide deck for an [introduction to async python](https://gitpitch.com/e
```python ```python
from taskcluster import client from taskcluster import client
qEvt = client.QueueEvents({rootUrl: 'https://tc.example.com'}) qEvt = client.QueueEvents()
# The following calls are equivalent # The following calls are equivalent
qEvt.taskCompleted({'taskId': 'atask'}) qEvt.taskCompleted({'taskId': 'atask'})
qEvt.taskCompleted(taskId='atask') qEvt.taskCompleted(taskId='atask')
``` ```
## Root URL
This client requires a `rootUrl` argument to identify the Taskcluster
deployment to talk to. As of this writing, the production cluster has rootUrl
`https://taskcluster.net`.
## Environment Variables
As of version 6.0.0, the client does not read the standard `TASKCLUSTER_…`
environment variables automatically. To fetch their values explicitly, use
`taskcluster.optionsFromEnvironment()`:
```python
auth = taskcluster.Auth(taskcluster.optionsFromEnvironment())
```
## Pagination ## Pagination
There are two ways to accomplish pagination easily with the python client. The first is There are two ways to accomplish pagination easily with the python client. The first is
to implement pagination in your code: to implement pagination in your code:
```python ```python
import taskcluster import taskcluster
queue = taskcluster.Queue({'rootUrl': 'https://tc.example.com'}) queue = taskcluster.Queue()
i = 0 i = 0
tasks = 0 tasks = 0
outcome = queue.listTaskGroup('JzTGxwxhQ76_Tt1dxkaG5g') outcome = queue.listTaskGroup('JzTGxwxhQ76_Tt1dxkaG5g')
@ -172,7 +153,7 @@ built and then counted:
```python ```python
import taskcluster import taskcluster
queue = taskcluster.Queue({'rootUrl': 'https://tc.example.com'}) queue = taskcluster.Queue()
responses = [] responses = []
@ -1030,9 +1011,9 @@ await asyncAuth.testAuthenticateGet() # -> result
import taskcluster import taskcluster
authEvents = taskcluster.AuthEvents(options) authEvents = taskcluster.AuthEvents(options)
``` ```
The auth service is responsible for storing credentials, managing The auth service, typically available at `auth.taskcluster.net`
assignment of scopes, and validation of request signatures from other is responsible for storing credentials, managing assignment of scopes,
services. and validation of request signatures from other services.
These exchanges provides notifications when credentials or roles are These exchanges provides notifications when credentials or roles are
updated. This is mostly so that multiple instances of the auth service updated. This is mostly so that multiple instances of the auth service
@ -1507,23 +1488,11 @@ session = taskcluster.aio.createSession(loop=loop)
asyncEC2Manager = taskcluster.aio.EC2Manager(options, session=session) asyncEC2Manager = taskcluster.aio.EC2Manager(options, session=session)
``` ```
A taskcluster service which manages EC2 instances. This service does not understand any taskcluster concepts intrinsicaly other than using the name `workerType` to refer to a group of associated instances. Unless you are working on building a provisioner for AWS, you almost certainly do not want to use this service A taskcluster service which manages EC2 instances. This service does not understand any taskcluster concepts intrinsicaly other than using the name `workerType` to refer to a group of associated instances. Unless you are working on building a provisioner for AWS, you almost certainly do not want to use this service
#### Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
```python
# Sync calls
eC2Manager.ping() # -> None`
# Async call
await asyncEC2Manager.ping() # -> None
```
#### See the list of worker types which are known to be managed #### See the list of worker types which are known to be managed
This method is only for debugging the ec2-manager This method is only for debugging the ec2-manager
Required [output schema](v1/list-worker-types.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#)
```python ```python
# Sync calls # Sync calls
@ -1541,7 +1510,7 @@ Takes the following arguments:
* `workerType` * `workerType`
Required [input schema](v1/run-instance-request.json#) Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#)
```python ```python
# Sync calls # Sync calls
@ -1579,7 +1548,7 @@ Takes the following arguments:
* `workerType` * `workerType`
Required [output schema](v1/worker-type-resources.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#)
```python ```python
# Sync calls # Sync calls
@ -1599,7 +1568,7 @@ Takes the following arguments:
* `workerType` * `workerType`
Required [output schema](v1/health.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/health.json#)
```python ```python
# Sync calls # Sync calls
@ -1619,7 +1588,7 @@ Takes the following arguments:
* `workerType` * `workerType`
Required [output schema](v1/errors.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/errors.json#)
```python ```python
# Sync calls # Sync calls
@ -1639,7 +1608,7 @@ Takes the following arguments:
* `workerType` * `workerType`
Required [output schema](v1/worker-type-state.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#)
```python ```python
# Sync calls # Sync calls
@ -1659,7 +1628,7 @@ Takes the following arguments:
* `name` * `name`
Required [input schema](v1/create-key-pair.json#) Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#)
```python ```python
# Sync calls # Sync calls
@ -1711,7 +1680,7 @@ await asyncEC2Manager.terminateInstance(region='value', instanceId='value') # ->
Return a list of possible prices for EC2 Return a list of possible prices for EC2
Required [output schema](v1/prices.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/prices.json#)
```python ```python
# Sync calls # Sync calls
@ -1724,9 +1693,9 @@ await asyncEC2Manager.getPrices() # -> result
Return a list of possible prices for EC2 Return a list of possible prices for EC2
Required [input schema](v1/prices-request.json#) Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#)
Required [output schema](v1/prices.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/prices.json#)
```python ```python
# Sync calls # Sync calls
@ -1739,7 +1708,7 @@ await asyncEC2Manager.getSpecificPrices(payload) # -> result
Give some basic stats on the health of our EC2 account Give some basic stats on the health of our EC2 account
Required [output schema](v1/health.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/health.json#)
```python ```python
# Sync calls # Sync calls
@ -1752,7 +1721,7 @@ await asyncEC2Manager.getHealth() # -> result
Return a list of recent errors encountered Return a list of recent errors encountered
Required [output schema](v1/errors.json#) Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/errors.json#)
```python ```python
# Sync calls # Sync calls
@ -1852,6 +1821,29 @@ eC2Manager.purgeQueues() # -> None`
await asyncEC2Manager.purgeQueues() # -> None await asyncEC2Manager.purgeQueues() # -> None
``` ```
#### API Reference
Generate an API reference for this service
```python
# Sync calls
eC2Manager.apiReference() # -> None`
# Async call
await asyncEC2Manager.apiReference() # -> None
```
#### Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
```python
# Sync calls
eC2Manager.ping() # -> None`
# Async call
await asyncEC2Manager.ping() # -> None
```
@ -1868,8 +1860,9 @@ loop = asyncio.get_event_loop()
session = taskcluster.aio.createSession(loop=loop) session = taskcluster.aio.createSession(loop=loop)
asyncGithub = taskcluster.aio.Github(options, session=session) asyncGithub = taskcluster.aio.Github(options, session=session)
``` ```
The github service is responsible for creating tasks in reposnse The github service, typically available at
to GitHub events, and posting results to the GitHub UI. `github.taskcluster.net`, is responsible for publishing pulse
messages in response to GitHub events.
This document describes the API end-point for consuming GitHub This document describes the API end-point for consuming GitHub
web hooks, as well as some useful consumer APIs. web hooks, as well as some useful consumer APIs.
@ -2063,12 +2056,6 @@ github service
* `organization` is required Description: The GitHub `organization` which had an event. All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped. * `organization` is required Description: The GitHub `organization` which had an event. All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped.
* `repository` is required Description: The GitHub `repository` which had an event.All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped. * `repository` is required Description: The GitHub `repository` which had an event.All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped.
#### GitHub release Event
* `githubEvents.taskGroupDefined(routingKeyPattern) -> routingKey`
* `routingKeyKind` is constant of `primary` is required Description: Identifier for the routing-key kind. This is always `"primary"` for the formalized routing key.
* `organization` is required Description: The GitHub `organization` which had an event. All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped.
* `repository` is required Description: The GitHub `repository` which had an event.All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped.
@ -2102,7 +2089,7 @@ https://www.npmjs.com/package/cron-parser. For example:
* `['0 0 9,21 * * 1-5', '0 0 12 * * 0,6']` -- weekdays at 9:00 and 21:00 UTC, weekends at noon * `['0 0 9,21 * * 1-5', '0 0 12 * * 0,6']` -- weekdays at 9:00 and 21:00 UTC, weekends at noon
The task definition is used as a JSON-e template, with a context depending on how it is fired. See The task definition is used as a JSON-e template, with a context depending on how it is fired. See
[/docs/reference/core/taskcluster-hooks/docs/firing-hooks](firing-hooks) https://docs.taskcluster.net/reference/core/taskcluster-hooks/docs/firing-hooks
for more information. for more information.
#### Ping Server #### Ping Server
Respond without doing anything. Respond without doing anything.
@ -2636,18 +2623,6 @@ asyncLogin = taskcluster.aio.Login(options, session=session)
``` ```
The Login service serves as the interface between external authentication The Login service serves as the interface between external authentication
systems and Taskcluster credentials. systems and Taskcluster credentials.
#### Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
```python
# Sync calls
login.ping() # -> None`
# Async call
await asyncLogin.ping() # -> None
```
#### Get Taskcluster credentials given a suitable `access_token` #### Get Taskcluster credentials given a suitable `access_token`
Given an OIDC `access_token` from a trusted OpenID provider, return a Given an OIDC `access_token` from a trusted OpenID provider, return a
set of Taskcluster credentials for use on behalf of the identified set of Taskcluster credentials for use on behalf of the identified
@ -2661,7 +2636,7 @@ Authorization: Bearer abc.xyz
``` ```
The `access_token` is first verified against the named The `access_token` is first verified against the named
:provider, then passed to the provider's APIBuilder to retrieve a user :provider, then passed to the provider's API to retrieve a user
profile. That profile is then used to generate Taskcluster credentials profile. That profile is then used to generate Taskcluster credentials
appropriate to the user. Note that the resulting credentials may or may appropriate to the user. Note that the resulting credentials may or may
not include a `certificate` property. Callers should be prepared for either not include a `certificate` property. Callers should be prepared for either
@ -2677,7 +2652,7 @@ Takes the following arguments:
* `provider` * `provider`
Required [output schema](v1/oidc-credentials-response.json#) Required [output schema](http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json)
```python ```python
# Sync calls # Sync calls
@ -2688,6 +2663,18 @@ await asyncLogin.oidcCredentials(provider) # -> result
await asyncLogin.oidcCredentials(provider='value') # -> result await asyncLogin.oidcCredentials(provider='value') # -> result
``` ```
#### Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
```python
# Sync calls
login.ping() # -> None`
# Async call
await asyncLogin.ping() # -> None
```
@ -2774,111 +2761,6 @@ await asyncNotify.irc(payload) # -> None
### Methods in `taskcluster.Pulse`
```python
import asyncio # Only for async
// Create Pulse client instance
import taskcluster
import taskcluster.aio
pulse = taskcluster.Pulse(options)
# Below only for async instances, assume already in coroutine
loop = asyncio.get_event_loop()
session = taskcluster.aio.createSession(loop=loop)
asyncPulse = taskcluster.aio.Pulse(options, session=session)
```
The taskcluster-pulse service, typically available at `pulse.taskcluster.net`
manages pulse credentials for taskcluster users.
A service to manage Pulse credentials for anything using
Taskcluster credentials. This allows for self-service pulse
access and greater control within the Taskcluster project.
#### Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
```python
# Sync calls
pulse.ping() # -> None`
# Async call
await asyncPulse.ping() # -> None
```
#### List Namespaces
List the namespaces managed by this service.
This will list up to 1000 namespaces. If more namespaces are present a
`continuationToken` will be returned, which can be given in the next
request. For the initial request, do not provide continuation token.
Required [output schema](v1/list-namespaces-response.json#)
```python
# Sync calls
pulse.listNamespaces() # -> result`
# Async call
await asyncPulse.listNamespaces() # -> result
```
#### Get a namespace
Get public information about a single namespace. This is the same information
as returned by `listNamespaces`.
Takes the following arguments:
* `namespace`
Required [output schema](v1/namespace.json#)
```python
# Sync calls
pulse.namespace(namespace) # -> result`
pulse.namespace(namespace='value') # -> result
# Async call
await asyncPulse.namespace(namespace) # -> result
await asyncPulse.namespace(namespace='value') # -> result
```
#### Claim a namespace
Claim a namespace, returning a connection string with access to that namespace
good for use until the `reclaimAt` time in the response body. The connection
string can be used as many times as desired during this period, but must not
be used after `reclaimAt`.
Connections made with this connection string may persist beyond `reclaimAt`,
although it should not persist forever. 24 hours is a good maximum, and this
service will terminate connections after 72 hours (although this value is
configurable).
The specified `expires` time updates any existing expiration times. Connections
for expired namespaces will be terminated.
Takes the following arguments:
* `namespace`
Required [input schema](v1/namespace-request.json#)
Required [output schema](v1/namespace-response.json#)
```python
# Sync calls
pulse.claimNamespace(namespace, payload) # -> result`
pulse.claimNamespace(payload, namespace='value') # -> result
# Async call
await asyncPulse.claimNamespace(namespace, payload) # -> result
await asyncPulse.claimNamespace(payload, namespace='value') # -> result
```
### Methods in `taskcluster.PurgeCache` ### Methods in `taskcluster.PurgeCache`
```python ```python
import asyncio # Only for async import asyncio # Only for async
@ -2892,7 +2774,8 @@ loop = asyncio.get_event_loop()
session = taskcluster.aio.createSession(loop=loop) session = taskcluster.aio.createSession(loop=loop)
asyncPurgeCache = taskcluster.aio.PurgeCache(options, session=session) asyncPurgeCache = taskcluster.aio.PurgeCache(options, session=session)
``` ```
The purge-cache service is responsible for publishing a pulse The purge-cache service, typically available at
`purge-cache.taskcluster.net`, is responsible for publishing a pulse
message for workers, so they can purge cache upon request. message for workers, so they can purge cache upon request.
This document describes the API end-point for publishing the pulse This document describes the API end-point for publishing the pulse

3
third_party/python/taskcluster/setup.py поставляемый
Просмотреть файл

@ -7,7 +7,7 @@ import sys
# The VERSION variable is automagically changed # The VERSION variable is automagically changed
# by release.sh. Make sure you understand how # by release.sh. Make sure you understand how
# that script works if you want to change this # that script works if you want to change this
VERSION = '6.0.0' VERSION = '4.0.1'
tests_require = [ tests_require = [
'nose==1.3.7', 'nose==1.3.7',
@ -30,7 +30,6 @@ install_requires = [
'requests>=2.4.3,<3', 'requests>=2.4.3,<3',
'mohawk>=0.3.4,<0.4', 'mohawk>=0.3.4,<0.4',
'slugid>=1.0.7,<2', 'slugid>=1.0.7,<2',
'taskcluster-urls>=10.1.0,<12',
'six>=1.10.0,<2', 'six>=1.10.0,<2',
] ]

Просмотреть файл

@ -9,7 +9,6 @@ from .hooks import Hooks # NOQA
from .index import Index # NOQA from .index import Index # NOQA
from .login import Login # NOQA from .login import Login # NOQA
from .notify import Notify # NOQA from .notify import Notify # NOQA
from .pulse import Pulse # NOQA
from .purgecache import PurgeCache # NOQA from .purgecache import PurgeCache # NOQA
from .purgecacheevents import PurgeCacheEvents # NOQA from .purgecacheevents import PurgeCacheEvents # NOQA
from .queue import Queue # NOQA from .queue import Queue # NOQA

Просмотреть файл

@ -9,7 +9,6 @@ from .hooks import Hooks # NOQA
from .index import Index # NOQA from .index import Index # NOQA
from .login import Login # NOQA from .login import Login # NOQA
from .notify import Notify # NOQA from .notify import Notify # NOQA
from .pulse import Pulse # NOQA
from .purgecache import PurgeCache # NOQA from .purgecache import PurgeCache # NOQA
from .purgecacheevents import PurgeCacheEvents # NOQA from .purgecacheevents import PurgeCacheEvents # NOQA
from .queue import Queue # NOQA from .queue import Queue # NOQA

Просмотреть файл

@ -114,7 +114,7 @@ class AsyncBaseClient(BaseClient):
the logic about doing failure retry and passes off the actual work the logic about doing failure retry and passes off the actual work
of doing an HTTP request to another method.""" of doing an HTTP request to another method."""
url = self._constructUrl(route) url = self._joinBaseUrlAndRoute(route)
log.debug('Full URL used is: %s', url) log.debug('Full URL used is: %s', url)
hawkExt = self.makeHawkExt() hawkExt = self.makeHawkExt()
@ -221,7 +221,7 @@ class AsyncBaseClient(BaseClient):
try: try:
await response.release() await response.release()
return await response.json() return await response.json()
except (ValueError, aiohttp.client_exceptions.ContentTypeError): except ValueError:
return {"response": response} return {"response": response}
# This code-path should be unreachable # This code-path should be unreachable
@ -239,8 +239,6 @@ class AsyncBaseClient(BaseClient):
def createApiClient(name, api): def createApiClient(name, api):
api = api['reference']
attributes = dict( attributes = dict(
name=name, name=name,
__doc__=api.get('description'), __doc__=api.get('description'),
@ -248,22 +246,12 @@ def createApiClient(name, api):
funcinfo={}, funcinfo={},
) )
# apply a default for apiVersion; this can be removed when all services copiedOptions = ('baseUrl', 'exchangePrefix')
# have apiVersion
if 'apiVersion' not in api:
api['apiVersion'] = 'v1'
copiedOptions = ('exchangePrefix',)
for opt in copiedOptions: for opt in copiedOptions:
if opt in api: if opt in api['reference']:
attributes['classOptions'][opt] = api[opt] attributes['classOptions'][opt] = api['reference'][opt]
copiedProperties = ('serviceName', 'apiVersion') for entry in api['reference']['entries']:
for opt in copiedProperties:
if opt in api:
attributes[opt] = api[opt]
for entry in api['entries']:
if entry['type'] == 'function': if entry['type'] == 'function':
def addApiCall(e): def addApiCall(e):
async def apiCall(self, *args, **kwargs): async def apiCall(self, *args, **kwargs):

Просмотреть файл

@ -111,6 +111,6 @@ async def putFile(filename, url, contentType, session=None):
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
contentLength = os.fstat(f.fileno()).st_size contentLength = os.fstat(f.fileno()).st_size
return await makeHttpRequest('put', url, f, headers={ return await makeHttpRequest('put', url, f, headers={
'Content-Length': str(contentLength), 'Content-Length': contentLength,
'Content-Type': contentType, 'Content-Type': contentType,
}, session=session) }, session=session)

Просмотреть файл

@ -55,9 +55,8 @@ class Auth(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://auth.taskcluster.net/v1/"
} }
serviceName = 'auth'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -13,9 +13,9 @@ _defaultConfig = config
class AuthEvents(AsyncBaseClient): class AuthEvents(AsyncBaseClient):
""" """
The auth service is responsible for storing credentials, managing The auth service, typically available at `auth.taskcluster.net`
assignment of scopes, and validation of request signatures from other is responsible for storing credentials, managing assignment of scopes,
services. and validation of request signatures from other services.
These exchanges provides notifications when credentials or roles are These exchanges provides notifications when credentials or roles are
updated. This is mostly so that multiple instances of the auth service updated. This is mostly so that multiple instances of the auth service
@ -24,10 +24,8 @@ class AuthEvents(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-auth/v1/", "exchangePrefix": "exchange/taskcluster-auth/v1/"
} }
serviceName = 'auth'
apiVersion = 'v1'
def clientCreated(self, *args, **kwargs): def clientCreated(self, *args, **kwargs):
""" """

Просмотреть файл

@ -44,9 +44,8 @@ class AwsProvisioner(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://aws-provisioner.taskcluster.net/v1"
} }
serviceName = 'aws-provisioner'
apiVersion = 'v1'
async def listWorkerTypeSummaries(self, *args, **kwargs): async def listWorkerTypeSummaries(self, *args, **kwargs):
""" """

Просмотреть файл

@ -17,9 +17,8 @@ class AwsProvisionerEvents(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/", "exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/"
} }
apiVersion = 'v1'
def workerTypeCreated(self, *args, **kwargs): def workerTypeCreated(self, *args, **kwargs):
""" """

Просмотреть файл

@ -17,21 +17,8 @@ class EC2Manager(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://ec2-manager.taskcluster.net/v1"
} }
serviceName = 'ec2-manager'
apiVersion = 'v1'
async def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
async def listWorkerTypes(self, *args, **kwargs): async def listWorkerTypes(self, *args, **kwargs):
""" """
@ -39,7 +26,7 @@ class EC2Manager(AsyncBaseClient):
This method is only for debugging the ec2-manager This method is only for debugging the ec2-manager
This method gives output: ``v1/list-worker-types.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -52,7 +39,7 @@ class EC2Manager(AsyncBaseClient):
Request an instance of a worker type Request an instance of a worker type
This method takes input: ``v1/run-instance-request.json#`` This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -76,7 +63,7 @@ class EC2Manager(AsyncBaseClient):
Return an object which has a generic state description. This only contains counts of instances Return an object which has a generic state description. This only contains counts of instances
This method gives output: ``v1/worker-type-resources.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -89,7 +76,7 @@ class EC2Manager(AsyncBaseClient):
Return a view of the health of a given worker type Return a view of the health of a given worker type
This method gives output: ``v1/health.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -102,7 +89,7 @@ class EC2Manager(AsyncBaseClient):
Return a list of the most recent errors encountered by a worker type Return a list of the most recent errors encountered by a worker type
This method gives output: ``v1/errors.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -115,7 +102,7 @@ class EC2Manager(AsyncBaseClient):
Return state information for a given worker type Return state information for a given worker type
This method gives output: ``v1/worker-type-state.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -128,7 +115,7 @@ class EC2Manager(AsyncBaseClient):
Idempotently ensure that a keypair of a given name exists Idempotently ensure that a keypair of a given name exists
This method takes input: ``v1/create-key-pair.json#`` This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -163,7 +150,7 @@ class EC2Manager(AsyncBaseClient):
Return a list of possible prices for EC2 Return a list of possible prices for EC2
This method gives output: ``v1/prices.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -176,9 +163,9 @@ class EC2Manager(AsyncBaseClient):
Return a list of possible prices for EC2 Return a list of possible prices for EC2
This method takes input: ``v1/prices-request.json#`` This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#``
This method gives output: ``v1/prices.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -191,7 +178,7 @@ class EC2Manager(AsyncBaseClient):
Give some basic stats on the health of our EC2 account Give some basic stats on the health of our EC2 account
This method gives output: ``v1/health.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -204,7 +191,7 @@ class EC2Manager(AsyncBaseClient):
Return a list of recent errors encountered Return a list of recent errors encountered
This method gives output: ``v1/errors.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -302,6 +289,29 @@ class EC2Manager(AsyncBaseClient):
return await self._makeApiCall(self.funcinfo["purgeQueues"], *args, **kwargs) return await self._makeApiCall(self.funcinfo["purgeQueues"], *args, **kwargs)
async def apiReference(self, *args, **kwargs):
"""
API Reference
Generate an API reference for this service
This method is ``experimental``
"""
return await self._makeApiCall(self.funcinfo["apiReference"], *args, **kwargs)
async def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
funcinfo = { funcinfo = {
"allState": { "allState": {
'args': [], 'args': [],
@ -317,6 +327,13 @@ class EC2Manager(AsyncBaseClient):
'route': '/internal/ami-usage', 'route': '/internal/ami-usage',
'stability': 'experimental', 'stability': 'experimental',
}, },
"apiReference": {
'args': [],
'method': 'get',
'name': 'apiReference',
'route': '/internal/api-reference',
'stability': 'experimental',
},
"dbpoolStats": { "dbpoolStats": {
'args': [], 'args': [],
'method': 'get', 'method': 'get',
@ -333,7 +350,7 @@ class EC2Manager(AsyncBaseClient):
}, },
"ensureKeyPair": { "ensureKeyPair": {
'args': ['name'], 'args': ['name'],
'input': 'v1/create-key-pair.json#', 'input': 'http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#',
'method': 'get', 'method': 'get',
'name': 'ensureKeyPair', 'name': 'ensureKeyPair',
'route': '/key-pairs/<name>', 'route': '/key-pairs/<name>',
@ -343,7 +360,7 @@ class EC2Manager(AsyncBaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'getHealth', 'name': 'getHealth',
'output': 'v1/health.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
'route': '/health', 'route': '/health',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -351,7 +368,7 @@ class EC2Manager(AsyncBaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'getPrices', 'name': 'getPrices',
'output': 'v1/prices.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
'route': '/prices', 'route': '/prices',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -359,16 +376,16 @@ class EC2Manager(AsyncBaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'getRecentErrors', 'name': 'getRecentErrors',
'output': 'v1/errors.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
'route': '/errors', 'route': '/errors',
'stability': 'experimental', 'stability': 'experimental',
}, },
"getSpecificPrices": { "getSpecificPrices": {
'args': [], 'args': [],
'input': 'v1/prices-request.json#', 'input': 'http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#',
'method': 'post', 'method': 'post',
'name': 'getSpecificPrices', 'name': 'getSpecificPrices',
'output': 'v1/prices.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
'route': '/prices', 'route': '/prices',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -376,7 +393,7 @@ class EC2Manager(AsyncBaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'listWorkerTypes', 'name': 'listWorkerTypes',
'output': 'v1/list-worker-types.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#',
'route': '/worker-types', 'route': '/worker-types',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -410,7 +427,7 @@ class EC2Manager(AsyncBaseClient):
}, },
"runInstance": { "runInstance": {
'args': ['workerType'], 'args': ['workerType'],
'input': 'v1/run-instance-request.json#', 'input': 'http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#',
'method': 'put', 'method': 'put',
'name': 'runInstance', 'name': 'runInstance',
'route': '/worker-types/<workerType>/instance', 'route': '/worker-types/<workerType>/instance',
@ -441,7 +458,7 @@ class EC2Manager(AsyncBaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeErrors', 'name': 'workerTypeErrors',
'output': 'v1/errors.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
'route': '/worker-types/<workerType>/errors', 'route': '/worker-types/<workerType>/errors',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -449,7 +466,7 @@ class EC2Manager(AsyncBaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeHealth', 'name': 'workerTypeHealth',
'output': 'v1/health.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
'route': '/worker-types/<workerType>/health', 'route': '/worker-types/<workerType>/health',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -457,7 +474,7 @@ class EC2Manager(AsyncBaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeState', 'name': 'workerTypeState',
'output': 'v1/worker-type-state.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#',
'route': '/worker-types/<workerType>/state', 'route': '/worker-types/<workerType>/state',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -465,7 +482,7 @@ class EC2Manager(AsyncBaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeStats', 'name': 'workerTypeStats',
'output': 'v1/worker-type-resources.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#',
'route': '/worker-types/<workerType>/stats', 'route': '/worker-types/<workerType>/stats',
'stability': 'experimental', 'stability': 'experimental',
}, },

Просмотреть файл

@ -13,8 +13,9 @@ _defaultConfig = config
class Github(AsyncBaseClient): class Github(AsyncBaseClient):
""" """
The github service is responsible for creating tasks in reposnse The github service, typically available at
to GitHub events, and posting results to the GitHub UI. `github.taskcluster.net`, is responsible for publishing pulse
messages in response to GitHub events.
This document describes the API end-point for consuming GitHub This document describes the API end-point for consuming GitHub
web hooks, as well as some useful consumer APIs. web hooks, as well as some useful consumer APIs.
@ -24,9 +25,8 @@ class Github(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://github.taskcluster.net/v1/"
} }
serviceName = 'github'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -22,10 +22,8 @@ class GithubEvents(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-github/v1/", "exchangePrefix": "exchange/taskcluster-github/v1/"
} }
serviceName = 'github'
apiVersion = 'v1'
def pullRequest(self, *args, **kwargs): def pullRequest(self, *args, **kwargs):
""" """
@ -150,43 +148,6 @@ class GithubEvents(AsyncBaseClient):
} }
return self._makeTopicExchange(ref, *args, **kwargs) return self._makeTopicExchange(ref, *args, **kwargs)
def taskGroupDefined(self, *args, **kwargs):
"""
GitHub release Event
used for creating status indicators in GitHub UI using Statuses API
This exchange outputs: ``v1/task-group-defined-message.json#``This exchange takes the following keys:
* routingKeyKind: Identifier for the routing-key kind. This is always `"primary"` for the formalized routing key. (required)
* organization: The GitHub `organization` which had an event. All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped. (required)
* repository: The GitHub `repository` which had an event.All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped. (required)
"""
ref = {
'exchange': 'task-group-defined',
'name': 'taskGroupDefined',
'routingKey': [
{
'constant': 'primary',
'multipleWords': False,
'name': 'routingKeyKind',
},
{
'multipleWords': False,
'name': 'organization',
},
{
'multipleWords': False,
'name': 'repository',
},
],
'schema': 'v1/task-group-defined-message.json#',
}
return self._makeTopicExchange(ref, *args, **kwargs)
funcinfo = { funcinfo = {
} }

Просмотреть файл

@ -30,14 +30,13 @@ class Hooks(AsyncBaseClient):
* `['0 0 9,21 * * 1-5', '0 0 12 * * 0,6']` -- weekdays at 9:00 and 21:00 UTC, weekends at noon * `['0 0 9,21 * * 1-5', '0 0 12 * * 0,6']` -- weekdays at 9:00 and 21:00 UTC, weekends at noon
The task definition is used as a JSON-e template, with a context depending on how it is fired. See The task definition is used as a JSON-e template, with a context depending on how it is fired. See
[/docs/reference/core/taskcluster-hooks/docs/firing-hooks](firing-hooks) https://docs.taskcluster.net/reference/core/taskcluster-hooks/docs/firing-hooks
for more information. for more information.
""" """
classOptions = { classOptions = {
"baseUrl": "https://hooks.taskcluster.net/v1/"
} }
serviceName = 'hooks'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -108,9 +108,8 @@ class Index(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://index.taskcluster.net/v1/"
} }
serviceName = 'index'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -18,21 +18,8 @@ class Login(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://login.taskcluster.net/v1"
} }
serviceName = 'login'
apiVersion = 'v1'
async def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
async def oidcCredentials(self, *args, **kwargs): async def oidcCredentials(self, *args, **kwargs):
""" """
@ -50,7 +37,7 @@ class Login(AsyncBaseClient):
``` ```
The `access_token` is first verified against the named The `access_token` is first verified against the named
:provider, then passed to the provider's APIBuilder to retrieve a user :provider, then passed to the provider's API to retrieve a user
profile. That profile is then used to generate Taskcluster credentials profile. That profile is then used to generate Taskcluster credentials
appropriate to the user. Note that the resulting credentials may or may appropriate to the user. Note that the resulting credentials may or may
not include a `certificate` property. Callers should be prepared for either not include a `certificate` property. Callers should be prepared for either
@ -60,19 +47,31 @@ class Login(AsyncBaseClient):
monitor this expiration and refresh the credentials if necessary, by calling monitor this expiration and refresh the credentials if necessary, by calling
this endpoint again, if they have expired. this endpoint again, if they have expired.
This method gives output: ``v1/oidc-credentials-response.json#`` This method gives output: ``http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json``
This method is ``experimental`` This method is ``experimental``
""" """
return await self._makeApiCall(self.funcinfo["oidcCredentials"], *args, **kwargs) return await self._makeApiCall(self.funcinfo["oidcCredentials"], *args, **kwargs)
async def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
funcinfo = { funcinfo = {
"oidcCredentials": { "oidcCredentials": {
'args': ['provider'], 'args': ['provider'],
'method': 'get', 'method': 'get',
'name': 'oidcCredentials', 'name': 'oidcCredentials',
'output': 'v1/oidc-credentials-response.json#', 'output': 'http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json',
'route': '/oidc-credentials/<provider>', 'route': '/oidc-credentials/<provider>',
'stability': 'experimental', 'stability': 'experimental',
}, },

Просмотреть файл

@ -19,9 +19,8 @@ class Notify(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://notify.taskcluster.net/v1/"
} }
serviceName = 'notify'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -22,21 +22,21 @@ class Pulse(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://pulse.taskcluster.net/v1"
} }
serviceName = 'pulse'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def overview(self, *args, **kwargs):
""" """
Ping Server Rabbit Overview
Respond without doing anything. Get an overview of the Rabbit cluster.
This endpoint is used to check that the service is up.
This method is ``stable`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json``
This method is ``experimental``
""" """
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs) return await self._makeApiCall(self.funcinfo["overview"], *args, **kwargs)
async def listNamespaces(self, *args, **kwargs): async def listNamespaces(self, *args, **kwargs):
""" """
@ -46,9 +46,9 @@ class Pulse(AsyncBaseClient):
This will list up to 1000 namespaces. If more namespaces are present a This will list up to 1000 namespaces. If more namespaces are present a
`continuationToken` will be returned, which can be given in the next `continuationToken` will be returned, which can be given in the next
request. For the initial request, do not provide continuation token. request. For the initial request, do not provide continuation.
This method gives output: ``v1/list-namespaces-response.json#`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json``
This method is ``experimental`` This method is ``experimental``
""" """
@ -62,7 +62,7 @@ class Pulse(AsyncBaseClient):
Get public information about a single namespace. This is the same information Get public information about a single namespace. This is the same information
as returned by `listNamespaces`. as returned by `listNamespaces`.
This method gives output: ``v1/namespace.json#`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace.json``
This method is ``experimental`` This method is ``experimental``
""" """
@ -73,35 +73,43 @@ class Pulse(AsyncBaseClient):
""" """
Claim a namespace Claim a namespace
Claim a namespace, returning a connection string with access to that namespace Claim a namespace, returning a username and password with access to that
good for use until the `reclaimAt` time in the response body. The connection namespace good for a short time. Clients should call this endpoint again
string can be used as many times as desired during this period, but must not at the re-claim time given in the response, as the password will be rotated
be used after `reclaimAt`. soon after that time. The namespace will expire, and any associated queues
and exchanges will be deleted, at the given expiration time.
Connections made with this connection string may persist beyond `reclaimAt`, The `expires` and `contact` properties can be updated at any time in a reclaim
although it should not persist forever. 24 hours is a good maximum, and this operation.
service will terminate connections after 72 hours (although this value is
configurable).
The specified `expires` time updates any existing expiration times. Connections This method takes input: ``http://schemas.taskcluster.net/pulse/v1/namespace-request.json``
for expired namespaces will be terminated.
This method takes input: ``v1/namespace-request.json#`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace-response.json``
This method gives output: ``v1/namespace-response.json#``
This method is ``experimental`` This method is ``experimental``
""" """
return await self._makeApiCall(self.funcinfo["claimNamespace"], *args, **kwargs) return await self._makeApiCall(self.funcinfo["claimNamespace"], *args, **kwargs)
async def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
funcinfo = { funcinfo = {
"claimNamespace": { "claimNamespace": {
'args': ['namespace'], 'args': ['namespace'],
'input': 'v1/namespace-request.json#', 'input': 'http://schemas.taskcluster.net/pulse/v1/namespace-request.json',
'method': 'post', 'method': 'post',
'name': 'claimNamespace', 'name': 'claimNamespace',
'output': 'v1/namespace-response.json#', 'output': 'http://schemas.taskcluster.net/pulse/v1/namespace-response.json',
'route': '/namespace/<namespace>', 'route': '/namespace/<namespace>',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -109,8 +117,8 @@ class Pulse(AsyncBaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'listNamespaces', 'name': 'listNamespaces',
'output': 'v1/list-namespaces-response.json#', 'output': 'http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json',
'query': ['limit', 'continuationToken'], 'query': ['limit', 'continuation'],
'route': '/namespaces', 'route': '/namespaces',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -118,10 +126,18 @@ class Pulse(AsyncBaseClient):
'args': ['namespace'], 'args': ['namespace'],
'method': 'get', 'method': 'get',
'name': 'namespace', 'name': 'namespace',
'output': 'v1/namespace.json#', 'output': 'http://schemas.taskcluster.net/pulse/v1/namespace.json',
'route': '/namespace/<namespace>', 'route': '/namespace/<namespace>',
'stability': 'experimental', 'stability': 'experimental',
}, },
"overview": {
'args': [],
'method': 'get',
'name': 'overview',
'output': 'http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json',
'route': '/overview',
'stability': 'experimental',
},
"ping": { "ping": {
'args': [], 'args': [],
'method': 'get', 'method': 'get',

Просмотреть файл

@ -13,7 +13,8 @@ _defaultConfig = config
class PurgeCache(AsyncBaseClient): class PurgeCache(AsyncBaseClient):
""" """
The purge-cache service is responsible for publishing a pulse The purge-cache service, typically available at
`purge-cache.taskcluster.net`, is responsible for publishing a pulse
message for workers, so they can purge cache upon request. message for workers, so they can purge cache upon request.
This document describes the API end-point for publishing the pulse This document describes the API end-point for publishing the pulse
@ -21,9 +22,8 @@ class PurgeCache(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://purge-cache.taskcluster.net/v1/"
} }
serviceName = 'purge-cache'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -22,10 +22,8 @@ class PurgeCacheEvents(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-purge-cache/v1/", "exchangePrefix": "exchange/taskcluster-purge-cache/v1/"
} }
serviceName = 'purge-cache'
apiVersion = 'v1'
def purgeCache(self, *args, **kwargs): def purgeCache(self, *args, **kwargs):
""" """

Просмотреть файл

@ -25,9 +25,8 @@ class Queue(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://queue.taskcluster.net/v1/"
} }
serviceName = 'queue'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -63,10 +63,8 @@ class QueueEvents(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-queue/v1/", "exchangePrefix": "exchange/taskcluster-queue/v1/"
} }
serviceName = 'queue'
apiVersion = 'v1'
def taskDefined(self, *args, **kwargs): def taskDefined(self, *args, **kwargs):
""" """

Просмотреть файл

@ -23,9 +23,8 @@ class Secrets(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://secrets.taskcluster.net/v1/"
} }
serviceName = 'secrets'
apiVersion = 'v1'
async def ping(self, *args, **kwargs): async def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -23,10 +23,8 @@ class TreeherderEvents(AsyncBaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-treeherder/v1/", "exchangePrefix": "exchange/taskcluster-treeherder/v1/"
} }
serviceName = 'treeherder'
apiVersion = 'v1'
def jobs(self, *args, **kwargs): def jobs(self, *args, **kwargs):
""" """
@ -35,7 +33,7 @@ class TreeherderEvents(AsyncBaseClient):
When a task run is scheduled or resolved, a message is posted to When a task run is scheduled or resolved, a message is posted to
this exchange in a Treeherder consumable format. this exchange in a Treeherder consumable format.
This exchange outputs: ``v1/pulse-job.json#``This exchange takes the following keys: This exchange outputs: ``http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#``This exchange takes the following keys:
* destination: destination (required) * destination: destination (required)
@ -61,7 +59,7 @@ class TreeherderEvents(AsyncBaseClient):
'name': 'reserved', 'name': 'reserved',
}, },
], ],
'schema': 'v1/pulse-job.json#', 'schema': 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#',
} }
return self._makeTopicExchange(ref, *args, **kwargs) return self._makeTopicExchange(ref, *args, **kwargs)

Просмотреть файл

@ -55,9 +55,8 @@ class Auth(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://auth.taskcluster.net/v1/"
} }
serviceName = 'auth'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -13,9 +13,9 @@ _defaultConfig = config
class AuthEvents(BaseClient): class AuthEvents(BaseClient):
""" """
The auth service is responsible for storing credentials, managing The auth service, typically available at `auth.taskcluster.net`
assignment of scopes, and validation of request signatures from other is responsible for storing credentials, managing assignment of scopes,
services. and validation of request signatures from other services.
These exchanges provides notifications when credentials or roles are These exchanges provides notifications when credentials or roles are
updated. This is mostly so that multiple instances of the auth service updated. This is mostly so that multiple instances of the auth service
@ -24,10 +24,8 @@ class AuthEvents(BaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-auth/v1/", "exchangePrefix": "exchange/taskcluster-auth/v1/"
} }
serviceName = 'auth'
apiVersion = 'v1'
def clientCreated(self, *args, **kwargs): def clientCreated(self, *args, **kwargs):
""" """

Просмотреть файл

@ -44,9 +44,8 @@ class AwsProvisioner(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://aws-provisioner.taskcluster.net/v1"
} }
serviceName = 'aws-provisioner'
apiVersion = 'v1'
def listWorkerTypeSummaries(self, *args, **kwargs): def listWorkerTypeSummaries(self, *args, **kwargs):
""" """

Просмотреть файл

@ -17,9 +17,8 @@ class AwsProvisionerEvents(BaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/", "exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/"
} }
apiVersion = 'v1'
def workerTypeCreated(self, *args, **kwargs): def workerTypeCreated(self, *args, **kwargs):
""" """

Просмотреть файл

@ -2,6 +2,7 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
import os
import json import json
import logging import logging
import copy import copy
@ -20,7 +21,6 @@ import mohawk.bewit
import taskcluster.exceptions as exceptions import taskcluster.exceptions as exceptions
import taskcluster.utils as utils import taskcluster.utils as utils
import taskcluster_urls as liburls
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -28,11 +28,10 @@ log = logging.getLogger(__name__)
# Default configuration # Default configuration
_defaultConfig = config = { _defaultConfig = config = {
'credentials': { 'credentials': {
'clientId': None, 'clientId': os.environ.get('TASKCLUSTER_CLIENT_ID'),
'accessToken': None, 'accessToken': os.environ.get('TASKCLUSTER_ACCESS_TOKEN'),
'certificate': None, 'certificate': os.environ.get('TASKCLUSTER_CERTIFICATE'),
}, },
'rootUrl': None,
'maxRetries': 5, 'maxRetries': 5,
'signedUrlExpiration': 15 * 60, 'signedUrlExpiration': 15 * 60,
} }
@ -53,14 +52,10 @@ class BaseClient(object):
""" """
def __init__(self, options=None, session=None): def __init__(self, options=None, session=None):
if options and options.get('baseUrl'):
raise exceptions.TaskclusterFailure('baseUrl option is no longer allowed')
o = copy.deepcopy(self.classOptions) o = copy.deepcopy(self.classOptions)
o.update(_defaultConfig) o.update(_defaultConfig)
if options: if options:
o.update(options) o.update(options)
if not o.get('rootUrl'):
raise exceptions.TaskclusterFailure('rootUrl option is required')
credentials = o.get('credentials') credentials = o.get('credentials')
if credentials: if credentials:
@ -72,7 +67,6 @@ class BaseClient(object):
except: except:
s = '%s (%s) must be unicode encodable' % (x, credentials[x]) s = '%s (%s) must be unicode encodable' % (x, credentials[x])
raise exceptions.TaskclusterAuthFailure(s) raise exceptions.TaskclusterAuthFailure(s)
self.options = o self.options = o
if 'credentials' in o: if 'credentials' in o:
log.debug('credentials key scrubbed from logging output') log.debug('credentials key scrubbed from logging output')
@ -174,7 +168,7 @@ class BaseClient(object):
route = self._subArgsInRoute(entry, routeParams) route = self._subArgsInRoute(entry, routeParams)
if query: if query:
route += '?' + urllib.parse.urlencode(query) route += '?' + urllib.parse.urlencode(query)
return liburls.api(self.options['rootUrl'], self.serviceName, self.apiVersion, route) return self._joinBaseUrlAndRoute(route)
def buildSignedUrl(self, methodName, *args, **kwargs): def buildSignedUrl(self, methodName, *args, **kwargs):
""" Build a signed URL. This URL contains the credentials needed to access """ Build a signed URL. This URL contains the credentials needed to access
@ -243,14 +237,11 @@ class BaseClient(object):
u.fragment, u.fragment,
)) ))
def _constructUrl(self, route): def _joinBaseUrlAndRoute(self, route):
"""Construct a URL for the given route on this service, based on the return urllib.parse.urljoin(
rootUrl""" '{}/'.format(self.options['baseUrl'].rstrip('/')),
return liburls.api( route.lstrip('/')
self.options['rootUrl'], )
self.serviceName,
self.apiVersion,
route.rstrip('/'))
def _makeApiCall(self, entry, *args, **kwargs): def _makeApiCall(self, entry, *args, **kwargs):
""" This function is used to dispatch calls to other functions """ This function is used to dispatch calls to other functions
@ -446,7 +437,7 @@ class BaseClient(object):
the logic about doing failure retry and passes off the actual work the logic about doing failure retry and passes off the actual work
of doing an HTTP request to another method.""" of doing an HTTP request to another method."""
url = self._constructUrl(route) url = self._joinBaseUrlAndRoute(route)
log.debug('Full URL used is: %s', url) log.debug('Full URL used is: %s', url)
hawkExt = self.makeHawkExt() hawkExt = self.makeHawkExt()
@ -554,8 +545,6 @@ class BaseClient(object):
def createApiClient(name, api): def createApiClient(name, api):
api = api['reference']
attributes = dict( attributes = dict(
name=name, name=name,
__doc__=api.get('description'), __doc__=api.get('description'),
@ -563,22 +552,12 @@ def createApiClient(name, api):
funcinfo={}, funcinfo={},
) )
# apply a default for apiVersion; this can be removed when all services copiedOptions = ('baseUrl', 'exchangePrefix')
# have apiVersion
if 'apiVersion' not in api:
api['apiVersion'] = 'v1'
copiedOptions = ('exchangePrefix',)
for opt in copiedOptions: for opt in copiedOptions:
if opt in api: if opt in api['reference']:
attributes['classOptions'][opt] = api[opt] attributes['classOptions'][opt] = api['reference'][opt]
copiedProperties = ('serviceName', 'apiVersion') for entry in api['reference']['entries']:
for opt in copiedProperties:
if opt in api:
attributes[opt] = api[opt]
for entry in api['entries']:
if entry['type'] == 'function': if entry['type'] == 'function':
def addApiCall(e): def addApiCall(e):
def apiCall(self, *args, **kwargs): def apiCall(self, *args, **kwargs):

Просмотреть файл

@ -17,21 +17,8 @@ class EC2Manager(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://ec2-manager.taskcluster.net/v1"
} }
serviceName = 'ec2-manager'
apiVersion = 'v1'
def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
def listWorkerTypes(self, *args, **kwargs): def listWorkerTypes(self, *args, **kwargs):
""" """
@ -39,7 +26,7 @@ class EC2Manager(BaseClient):
This method is only for debugging the ec2-manager This method is only for debugging the ec2-manager
This method gives output: ``v1/list-worker-types.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -52,7 +39,7 @@ class EC2Manager(BaseClient):
Request an instance of a worker type Request an instance of a worker type
This method takes input: ``v1/run-instance-request.json#`` This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -76,7 +63,7 @@ class EC2Manager(BaseClient):
Return an object which has a generic state description. This only contains counts of instances Return an object which has a generic state description. This only contains counts of instances
This method gives output: ``v1/worker-type-resources.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -89,7 +76,7 @@ class EC2Manager(BaseClient):
Return a view of the health of a given worker type Return a view of the health of a given worker type
This method gives output: ``v1/health.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -102,7 +89,7 @@ class EC2Manager(BaseClient):
Return a list of the most recent errors encountered by a worker type Return a list of the most recent errors encountered by a worker type
This method gives output: ``v1/errors.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -115,7 +102,7 @@ class EC2Manager(BaseClient):
Return state information for a given worker type Return state information for a given worker type
This method gives output: ``v1/worker-type-state.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -128,7 +115,7 @@ class EC2Manager(BaseClient):
Idempotently ensure that a keypair of a given name exists Idempotently ensure that a keypair of a given name exists
This method takes input: ``v1/create-key-pair.json#`` This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -163,7 +150,7 @@ class EC2Manager(BaseClient):
Return a list of possible prices for EC2 Return a list of possible prices for EC2
This method gives output: ``v1/prices.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -176,9 +163,9 @@ class EC2Manager(BaseClient):
Return a list of possible prices for EC2 Return a list of possible prices for EC2
This method takes input: ``v1/prices-request.json#`` This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#``
This method gives output: ``v1/prices.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -191,7 +178,7 @@ class EC2Manager(BaseClient):
Give some basic stats on the health of our EC2 account Give some basic stats on the health of our EC2 account
This method gives output: ``v1/health.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -204,7 +191,7 @@ class EC2Manager(BaseClient):
Return a list of recent errors encountered Return a list of recent errors encountered
This method gives output: ``v1/errors.json#`` This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
This method is ``experimental`` This method is ``experimental``
""" """
@ -302,6 +289,29 @@ class EC2Manager(BaseClient):
return self._makeApiCall(self.funcinfo["purgeQueues"], *args, **kwargs) return self._makeApiCall(self.funcinfo["purgeQueues"], *args, **kwargs)
def apiReference(self, *args, **kwargs):
"""
API Reference
Generate an API reference for this service
This method is ``experimental``
"""
return self._makeApiCall(self.funcinfo["apiReference"], *args, **kwargs)
def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
funcinfo = { funcinfo = {
"allState": { "allState": {
'args': [], 'args': [],
@ -317,6 +327,13 @@ class EC2Manager(BaseClient):
'route': '/internal/ami-usage', 'route': '/internal/ami-usage',
'stability': 'experimental', 'stability': 'experimental',
}, },
"apiReference": {
'args': [],
'method': 'get',
'name': 'apiReference',
'route': '/internal/api-reference',
'stability': 'experimental',
},
"dbpoolStats": { "dbpoolStats": {
'args': [], 'args': [],
'method': 'get', 'method': 'get',
@ -333,7 +350,7 @@ class EC2Manager(BaseClient):
}, },
"ensureKeyPair": { "ensureKeyPair": {
'args': ['name'], 'args': ['name'],
'input': 'v1/create-key-pair.json#', 'input': 'http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#',
'method': 'get', 'method': 'get',
'name': 'ensureKeyPair', 'name': 'ensureKeyPair',
'route': '/key-pairs/<name>', 'route': '/key-pairs/<name>',
@ -343,7 +360,7 @@ class EC2Manager(BaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'getHealth', 'name': 'getHealth',
'output': 'v1/health.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
'route': '/health', 'route': '/health',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -351,7 +368,7 @@ class EC2Manager(BaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'getPrices', 'name': 'getPrices',
'output': 'v1/prices.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
'route': '/prices', 'route': '/prices',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -359,16 +376,16 @@ class EC2Manager(BaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'getRecentErrors', 'name': 'getRecentErrors',
'output': 'v1/errors.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
'route': '/errors', 'route': '/errors',
'stability': 'experimental', 'stability': 'experimental',
}, },
"getSpecificPrices": { "getSpecificPrices": {
'args': [], 'args': [],
'input': 'v1/prices-request.json#', 'input': 'http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#',
'method': 'post', 'method': 'post',
'name': 'getSpecificPrices', 'name': 'getSpecificPrices',
'output': 'v1/prices.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
'route': '/prices', 'route': '/prices',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -376,7 +393,7 @@ class EC2Manager(BaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'listWorkerTypes', 'name': 'listWorkerTypes',
'output': 'v1/list-worker-types.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#',
'route': '/worker-types', 'route': '/worker-types',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -410,7 +427,7 @@ class EC2Manager(BaseClient):
}, },
"runInstance": { "runInstance": {
'args': ['workerType'], 'args': ['workerType'],
'input': 'v1/run-instance-request.json#', 'input': 'http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#',
'method': 'put', 'method': 'put',
'name': 'runInstance', 'name': 'runInstance',
'route': '/worker-types/<workerType>/instance', 'route': '/worker-types/<workerType>/instance',
@ -441,7 +458,7 @@ class EC2Manager(BaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeErrors', 'name': 'workerTypeErrors',
'output': 'v1/errors.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
'route': '/worker-types/<workerType>/errors', 'route': '/worker-types/<workerType>/errors',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -449,7 +466,7 @@ class EC2Manager(BaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeHealth', 'name': 'workerTypeHealth',
'output': 'v1/health.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
'route': '/worker-types/<workerType>/health', 'route': '/worker-types/<workerType>/health',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -457,7 +474,7 @@ class EC2Manager(BaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeState', 'name': 'workerTypeState',
'output': 'v1/worker-type-state.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#',
'route': '/worker-types/<workerType>/state', 'route': '/worker-types/<workerType>/state',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -465,7 +482,7 @@ class EC2Manager(BaseClient):
'args': ['workerType'], 'args': ['workerType'],
'method': 'get', 'method': 'get',
'name': 'workerTypeStats', 'name': 'workerTypeStats',
'output': 'v1/worker-type-resources.json#', 'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#',
'route': '/worker-types/<workerType>/stats', 'route': '/worker-types/<workerType>/stats',
'stability': 'experimental', 'stability': 'experimental',
}, },

Просмотреть файл

@ -13,8 +13,9 @@ _defaultConfig = config
class Github(BaseClient): class Github(BaseClient):
""" """
The github service is responsible for creating tasks in reposnse The github service, typically available at
to GitHub events, and posting results to the GitHub UI. `github.taskcluster.net`, is responsible for publishing pulse
messages in response to GitHub events.
This document describes the API end-point for consuming GitHub This document describes the API end-point for consuming GitHub
web hooks, as well as some useful consumer APIs. web hooks, as well as some useful consumer APIs.
@ -24,9 +25,8 @@ class Github(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://github.taskcluster.net/v1/"
} }
serviceName = 'github'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -22,10 +22,8 @@ class GithubEvents(BaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-github/v1/", "exchangePrefix": "exchange/taskcluster-github/v1/"
} }
serviceName = 'github'
apiVersion = 'v1'
def pullRequest(self, *args, **kwargs): def pullRequest(self, *args, **kwargs):
""" """
@ -150,43 +148,6 @@ class GithubEvents(BaseClient):
} }
return self._makeTopicExchange(ref, *args, **kwargs) return self._makeTopicExchange(ref, *args, **kwargs)
def taskGroupDefined(self, *args, **kwargs):
"""
GitHub release Event
used for creating status indicators in GitHub UI using Statuses API
This exchange outputs: ``v1/task-group-defined-message.json#``This exchange takes the following keys:
* routingKeyKind: Identifier for the routing-key kind. This is always `"primary"` for the formalized routing key. (required)
* organization: The GitHub `organization` which had an event. All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped. (required)
* repository: The GitHub `repository` which had an event.All periods have been replaced by % - such that foo.bar becomes foo%bar - and all other special characters aside from - and _ have been stripped. (required)
"""
ref = {
'exchange': 'task-group-defined',
'name': 'taskGroupDefined',
'routingKey': [
{
'constant': 'primary',
'multipleWords': False,
'name': 'routingKeyKind',
},
{
'multipleWords': False,
'name': 'organization',
},
{
'multipleWords': False,
'name': 'repository',
},
],
'schema': 'v1/task-group-defined-message.json#',
}
return self._makeTopicExchange(ref, *args, **kwargs)
funcinfo = { funcinfo = {
} }

Просмотреть файл

@ -30,14 +30,13 @@ class Hooks(BaseClient):
* `['0 0 9,21 * * 1-5', '0 0 12 * * 0,6']` -- weekdays at 9:00 and 21:00 UTC, weekends at noon * `['0 0 9,21 * * 1-5', '0 0 12 * * 0,6']` -- weekdays at 9:00 and 21:00 UTC, weekends at noon
The task definition is used as a JSON-e template, with a context depending on how it is fired. See The task definition is used as a JSON-e template, with a context depending on how it is fired. See
[/docs/reference/core/taskcluster-hooks/docs/firing-hooks](firing-hooks) https://docs.taskcluster.net/reference/core/taskcluster-hooks/docs/firing-hooks
for more information. for more information.
""" """
classOptions = { classOptions = {
"baseUrl": "https://hooks.taskcluster.net/v1/"
} }
serviceName = 'hooks'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -108,9 +108,8 @@ class Index(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://index.taskcluster.net/v1/"
} }
serviceName = 'index'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -18,21 +18,8 @@ class Login(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://login.taskcluster.net/v1"
} }
serviceName = 'login'
apiVersion = 'v1'
def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
def oidcCredentials(self, *args, **kwargs): def oidcCredentials(self, *args, **kwargs):
""" """
@ -50,7 +37,7 @@ class Login(BaseClient):
``` ```
The `access_token` is first verified against the named The `access_token` is first verified against the named
:provider, then passed to the provider's APIBuilder to retrieve a user :provider, then passed to the provider's API to retrieve a user
profile. That profile is then used to generate Taskcluster credentials profile. That profile is then used to generate Taskcluster credentials
appropriate to the user. Note that the resulting credentials may or may appropriate to the user. Note that the resulting credentials may or may
not include a `certificate` property. Callers should be prepared for either not include a `certificate` property. Callers should be prepared for either
@ -60,19 +47,31 @@ class Login(BaseClient):
monitor this expiration and refresh the credentials if necessary, by calling monitor this expiration and refresh the credentials if necessary, by calling
this endpoint again, if they have expired. this endpoint again, if they have expired.
This method gives output: ``v1/oidc-credentials-response.json#`` This method gives output: ``http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json``
This method is ``experimental`` This method is ``experimental``
""" """
return self._makeApiCall(self.funcinfo["oidcCredentials"], *args, **kwargs) return self._makeApiCall(self.funcinfo["oidcCredentials"], *args, **kwargs)
def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
funcinfo = { funcinfo = {
"oidcCredentials": { "oidcCredentials": {
'args': ['provider'], 'args': ['provider'],
'method': 'get', 'method': 'get',
'name': 'oidcCredentials', 'name': 'oidcCredentials',
'output': 'v1/oidc-credentials-response.json#', 'output': 'http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json',
'route': '/oidc-credentials/<provider>', 'route': '/oidc-credentials/<provider>',
'stability': 'experimental', 'stability': 'experimental',
}, },

Просмотреть файл

@ -19,9 +19,8 @@ class Notify(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://notify.taskcluster.net/v1/"
} }
serviceName = 'notify'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -22,21 +22,21 @@ class Pulse(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://pulse.taskcluster.net/v1"
} }
serviceName = 'pulse'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def overview(self, *args, **kwargs):
""" """
Ping Server Rabbit Overview
Respond without doing anything. Get an overview of the Rabbit cluster.
This endpoint is used to check that the service is up.
This method is ``stable`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json``
This method is ``experimental``
""" """
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs) return self._makeApiCall(self.funcinfo["overview"], *args, **kwargs)
def listNamespaces(self, *args, **kwargs): def listNamespaces(self, *args, **kwargs):
""" """
@ -46,9 +46,9 @@ class Pulse(BaseClient):
This will list up to 1000 namespaces. If more namespaces are present a This will list up to 1000 namespaces. If more namespaces are present a
`continuationToken` will be returned, which can be given in the next `continuationToken` will be returned, which can be given in the next
request. For the initial request, do not provide continuation token. request. For the initial request, do not provide continuation.
This method gives output: ``v1/list-namespaces-response.json#`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json``
This method is ``experimental`` This method is ``experimental``
""" """
@ -62,7 +62,7 @@ class Pulse(BaseClient):
Get public information about a single namespace. This is the same information Get public information about a single namespace. This is the same information
as returned by `listNamespaces`. as returned by `listNamespaces`.
This method gives output: ``v1/namespace.json#`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace.json``
This method is ``experimental`` This method is ``experimental``
""" """
@ -73,35 +73,43 @@ class Pulse(BaseClient):
""" """
Claim a namespace Claim a namespace
Claim a namespace, returning a connection string with access to that namespace Claim a namespace, returning a username and password with access to that
good for use until the `reclaimAt` time in the response body. The connection namespace good for a short time. Clients should call this endpoint again
string can be used as many times as desired during this period, but must not at the re-claim time given in the response, as the password will be rotated
be used after `reclaimAt`. soon after that time. The namespace will expire, and any associated queues
and exchanges will be deleted, at the given expiration time.
Connections made with this connection string may persist beyond `reclaimAt`, The `expires` and `contact` properties can be updated at any time in a reclaim
although it should not persist forever. 24 hours is a good maximum, and this operation.
service will terminate connections after 72 hours (although this value is
configurable).
The specified `expires` time updates any existing expiration times. Connections This method takes input: ``http://schemas.taskcluster.net/pulse/v1/namespace-request.json``
for expired namespaces will be terminated.
This method takes input: ``v1/namespace-request.json#`` This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace-response.json``
This method gives output: ``v1/namespace-response.json#``
This method is ``experimental`` This method is ``experimental``
""" """
return self._makeApiCall(self.funcinfo["claimNamespace"], *args, **kwargs) return self._makeApiCall(self.funcinfo["claimNamespace"], *args, **kwargs)
def ping(self, *args, **kwargs):
"""
Ping Server
Respond without doing anything.
This endpoint is used to check that the service is up.
This method is ``stable``
"""
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
funcinfo = { funcinfo = {
"claimNamespace": { "claimNamespace": {
'args': ['namespace'], 'args': ['namespace'],
'input': 'v1/namespace-request.json#', 'input': 'http://schemas.taskcluster.net/pulse/v1/namespace-request.json',
'method': 'post', 'method': 'post',
'name': 'claimNamespace', 'name': 'claimNamespace',
'output': 'v1/namespace-response.json#', 'output': 'http://schemas.taskcluster.net/pulse/v1/namespace-response.json',
'route': '/namespace/<namespace>', 'route': '/namespace/<namespace>',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -109,8 +117,8 @@ class Pulse(BaseClient):
'args': [], 'args': [],
'method': 'get', 'method': 'get',
'name': 'listNamespaces', 'name': 'listNamespaces',
'output': 'v1/list-namespaces-response.json#', 'output': 'http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json',
'query': ['limit', 'continuationToken'], 'query': ['limit', 'continuation'],
'route': '/namespaces', 'route': '/namespaces',
'stability': 'experimental', 'stability': 'experimental',
}, },
@ -118,10 +126,18 @@ class Pulse(BaseClient):
'args': ['namespace'], 'args': ['namespace'],
'method': 'get', 'method': 'get',
'name': 'namespace', 'name': 'namespace',
'output': 'v1/namespace.json#', 'output': 'http://schemas.taskcluster.net/pulse/v1/namespace.json',
'route': '/namespace/<namespace>', 'route': '/namespace/<namespace>',
'stability': 'experimental', 'stability': 'experimental',
}, },
"overview": {
'args': [],
'method': 'get',
'name': 'overview',
'output': 'http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json',
'route': '/overview',
'stability': 'experimental',
},
"ping": { "ping": {
'args': [], 'args': [],
'method': 'get', 'method': 'get',

Просмотреть файл

@ -13,7 +13,8 @@ _defaultConfig = config
class PurgeCache(BaseClient): class PurgeCache(BaseClient):
""" """
The purge-cache service is responsible for publishing a pulse The purge-cache service, typically available at
`purge-cache.taskcluster.net`, is responsible for publishing a pulse
message for workers, so they can purge cache upon request. message for workers, so they can purge cache upon request.
This document describes the API end-point for publishing the pulse This document describes the API end-point for publishing the pulse
@ -21,9 +22,8 @@ class PurgeCache(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://purge-cache.taskcluster.net/v1/"
} }
serviceName = 'purge-cache'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -22,10 +22,8 @@ class PurgeCacheEvents(BaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-purge-cache/v1/", "exchangePrefix": "exchange/taskcluster-purge-cache/v1/"
} }
serviceName = 'purge-cache'
apiVersion = 'v1'
def purgeCache(self, *args, **kwargs): def purgeCache(self, *args, **kwargs):
""" """

Просмотреть файл

@ -25,9 +25,8 @@ class Queue(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://queue.taskcluster.net/v1/"
} }
serviceName = 'queue'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -63,10 +63,8 @@ class QueueEvents(BaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-queue/v1/", "exchangePrefix": "exchange/taskcluster-queue/v1/"
} }
serviceName = 'queue'
apiVersion = 'v1'
def taskDefined(self, *args, **kwargs): def taskDefined(self, *args, **kwargs):
""" """

Просмотреть файл

@ -23,9 +23,8 @@ class Secrets(BaseClient):
""" """
classOptions = { classOptions = {
"baseUrl": "https://secrets.taskcluster.net/v1/"
} }
serviceName = 'secrets'
apiVersion = 'v1'
def ping(self, *args, **kwargs): def ping(self, *args, **kwargs):
""" """

Просмотреть файл

@ -23,10 +23,8 @@ class TreeherderEvents(BaseClient):
""" """
classOptions = { classOptions = {
"exchangePrefix": "exchange/taskcluster-treeherder/v1/", "exchangePrefix": "exchange/taskcluster-treeherder/v1/"
} }
serviceName = 'treeherder'
apiVersion = 'v1'
def jobs(self, *args, **kwargs): def jobs(self, *args, **kwargs):
""" """
@ -35,7 +33,7 @@ class TreeherderEvents(BaseClient):
When a task run is scheduled or resolved, a message is posted to When a task run is scheduled or resolved, a message is posted to
this exchange in a Treeherder consumable format. this exchange in a Treeherder consumable format.
This exchange outputs: ``v1/pulse-job.json#``This exchange takes the following keys: This exchange outputs: ``http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#``This exchange takes the following keys:
* destination: destination (required) * destination: destination (required)
@ -61,7 +59,7 @@ class TreeherderEvents(BaseClient):
'name': 'reserved', 'name': 'reserved',
}, },
], ],
'schema': 'v1/pulse-job.json#', 'schema': 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#',
} }
return self._makeTopicExchange(ref, *args, **kwargs) return self._makeTopicExchange(ref, *args, **kwargs)

Просмотреть файл

@ -1,4 +1,3 @@
# -*- coding: UTF-8 -*-
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
import re import re
import json import json
@ -11,6 +10,7 @@ import requests.exceptions
import slugid import slugid
import time import time
import six import six
import sys
import random import random
from . import exceptions from . import exceptions
@ -298,7 +298,7 @@ def putFile(filename, url, contentType):
with open(filename, 'rb') as f: with open(filename, 'rb') as f:
contentLength = os.fstat(f.fileno()).st_size contentLength = os.fstat(f.fileno()).st_size
return makeHttpRequest('put', url, f, headers={ return makeHttpRequest('put', url, f, headers={
'Content-Length': str(contentLength), 'Content-Length': contentLength,
'Content-Type': contentType, 'Content-Type': contentType,
}) })
@ -319,30 +319,89 @@ def isExpired(certificate):
return expiry < int(time.time() * 1000) + 20 * 60 return expiry < int(time.time() * 1000) + 20 * 60
def optionsFromEnvironment(defaults=None): def authenticate(description=None):
"""Fetch root URL and credentials from the standard TASKCLUSTER_… """
environment variables and return them in a format suitable for passing to a Open a web-browser to login.taskcluster.net and listen on localhost for
client constructor.""" a callback with credentials in query-string.
options = defaults or {}
credentials = options.get('credentials', {})
rootUrl = os.environ.get('TASKCLUSTER_ROOT_URL') The description will be shown on login.taskcluster.net, if not provided
if rootUrl: a default message with script path will be displayed.
options['rootUrl'] = rootUrl """
# Importing here to avoid loading these 'obscure' module before it's needed.
# Most clients won't use this feature, so we don't want issues with these
# modules to affect the library. Maybe they don't work in some environments
import webbrowser
from six.moves import urllib
from six.moves.urllib.parse import quote
import BaseHTTPServer
clientId = os.environ.get('TASKCLUSTER_CLIENT_ID') if not description:
if clientId: script = '[interpreter/unknown]'
credentials['clientId'] = clientId main = sys.modules.get('__main__', None)
if main and hasattr(main, '__file__'):
script = os.path.abspath(main.__file__)
description = (
"Python script: `%s`\n\nWould like some temporary credentials."
% script
)
accessToken = os.environ.get('TASKCLUSTER_ACCESS_TOKEN') creds = [None]
if accessToken:
credentials['accessToken'] = accessToken
certificate = os.environ.get('TASKCLUSTER_CERTIFICATE') class AuthCallBackRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
if certificate: def log_message(format, *args):
credentials['certificate'] = certificate pass
if credentials: def do_GET(self):
options['credentials'] = credentials url = urllib.parse.urlparse(self.path)
query = urllib.parse.parse_qs(url.query)
clientId = query.get('clientId', [None])[0]
accessToken = query.get('accessToken', [None])[0]
certificate = query.get('certificate', [None])[0]
hasCreds = clientId and accessToken and certificate
if hasCreds:
creds[0] = {
"clientId": clientId,
"accessToken": accessToken,
"certificate": certificate
}
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
if hasCreds:
self.wfile.write("""
<h1>Credentials transferred successfully</h1>
<i>You can close this window now.</i>
<script>window.close();</script>
""")
else:
self.wfile.write("""
<h1>Transfer of credentials failed!</h1>
<p>Something went wrong, you can navigate back and try again...</p>
""")
return
return options # Create server on localhost at random port
retries = 5
while retries > 0:
try:
server = BaseHTTPServer.HTTPServer(('', 0), AuthCallBackRequestHandler)
except:
retries -= 1
break
port = server.server_address[1]
query = "?target=" + quote('http://localhost:' + str(port), '')
query += "&description=" + quote(description, '')
webbrowser.open('https://login.taskcluster.net' + query, 1, True)
print("")
print("-------------------------------------------------------")
print(" Opening browser window to login.taskcluster.net")
print(" Asking you to grant temporary credentials to:")
print(" http://localhost:" + str(port))
print("-------------------------------------------------------")
print("")
while not creds[0]:
server.handle_request()
return creds[0]

Просмотреть файл

@ -21,7 +21,6 @@ class TestAuthenticationAsync(base.TCTest):
async def x(): async def x():
async with subjectAsync.createSession(loop=loop) as session: async with subjectAsync.createSession(loop=loop) as session:
client = subjectAsync.Auth({ client = subjectAsync.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'no-secret', 'accessToken': 'no-secret',
@ -50,14 +49,13 @@ class TestAuthenticationAsync(base.TCTest):
['test:xyz'], ['test:xyz'],
) )
client = subjectAsync.Auth({ client = subjectAsync.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
}, session=session) }, session=session)
result = await client.testAuthenticate({ result = client.testAuthenticate({
'clientScopes': ['test:*'], 'clientScopes': ['test:*'],
'requiredScopes': ['test:xyz'], 'requiredScopes': ['test:xyz'],
}) })
self.assertEqual(result, {'scopes': ['test:xyz'], 'clientId': 'tester'}) self.assertEqual(result, {'scopes': ['test:xyz'], 'clientId': 'tester'})
loop.run_until_complete(x()) loop.run_until_complete

Просмотреть файл

@ -7,7 +7,6 @@ from six.moves import urllib
import os import os
import re import re
import json import json
import copy
import mock import mock
import httmock import httmock
@ -17,7 +16,6 @@ import base
import taskcluster.auth as subject import taskcluster.auth as subject
import taskcluster.exceptions as exc import taskcluster.exceptions as exc
import taskcluster.utils as utils import taskcluster.utils as utils
import taskcluster_urls as liburls
class ClientTest(base.TCTest): class ClientTest(base.TCTest):
@ -47,7 +45,7 @@ class ClientTest(base.TCTest):
] ]
self.apiRef = base.createApiRef(entries=entries) self.apiRef = base.createApiRef(entries=entries)
self.clientClass = subject.createApiClient('testApi', self.apiRef) self.clientClass = subject.createApiClient('testApi', self.apiRef)
self.client = self.clientClass({'rootUrl': self.test_root_url}) self.client = self.clientClass()
# Patch time.sleep so that we don't delay tests # Patch time.sleep so that we don't delay tests
sleepPatcher = mock.patch('time.sleep') sleepPatcher = mock.patch('time.sleep')
sleepSleep = sleepPatcher.start() sleepSleep = sleepPatcher.start()
@ -58,32 +56,6 @@ class ClientTest(base.TCTest):
time.sleep = self.realTimeSleep time.sleep = self.realTimeSleep
class TestConstructorOptions(ClientTest):
def test_baseUrl_not_allowed(self):
with self.assertRaises(exc.TaskclusterFailure):
self.clientClass({'baseUrl': 'https://bogus.net'})
def test_rootUrl_set_correctly(self):
client = self.clientClass({'rootUrl': self.test_root_url})
self.assertEqual(client.options['rootUrl'], self.test_root_url)
def test_apiVersion_set_correctly(self):
client = self.clientClass({'rootUrl': self.test_root_url})
self.assertEqual(client.apiVersion, 'v1')
def test_apiVersion_set_correctly_default(self):
apiRef = copy.deepcopy(self.apiRef)
del apiRef['reference']['apiVersion']
clientClass = subject.createApiClient('testApi', apiRef)
client = clientClass({'rootUrl': self.test_root_url})
self.assertEqual(client.apiVersion, 'v1')
def test_serviceName_set_correctly(self):
client = self.clientClass({'rootUrl': self.test_root_url})
self.assertEqual(client.serviceName, 'fake')
class TestSubArgsInRoute(ClientTest): class TestSubArgsInRoute(ClientTest):
def test_valid_no_subs(self): def test_valid_no_subs(self):
@ -196,10 +168,10 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_1_with_payload(self): def test_calling_convention_1_with_payload(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test', 'input': True}, {'args': ['k1', 'k2'], 'name': 'test', 'input': True},
1, 1,
2, 2,
{'A': 123} {'A': 123}
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, {'A': 123}) self.assertEqual(payload, {'A': 123})
@ -213,8 +185,8 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_2_with_payload(self): def test_calling_convention_2_with_payload(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test', 'input': True}, {'args': ['k1', 'k2'], 'name': 'test', 'input': True},
{'A': 123}, k1=1, k2=2 {'A': 123}, k1=1, k2=2
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, {'A': 123}) self.assertEqual(payload, {'A': 123})
@ -222,8 +194,8 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_3_without_payload_without_query(self): def test_calling_convention_3_without_payload_without_query(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test'}, {'args': ['k1', 'k2'], 'name': 'test'},
params={'k1': 1, 'k2': 2} params={'k1': 1, 'k2': 2}
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, None) self.assertEqual(payload, None)
@ -231,9 +203,9 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_3_with_payload_without_query(self): def test_calling_convention_3_with_payload_without_query(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test'}, {'args': ['k1', 'k2'], 'name': 'test'},
params={'k1': 1, 'k2': 2}, params={'k1': 1, 'k2': 2},
payload={'A': 123} payload={'A': 123}
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, {'A': 123}) self.assertEqual(payload, {'A': 123})
@ -241,10 +213,10 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_3_with_payload_with_query(self): def test_calling_convention_3_with_payload_with_query(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test'}, {'args': ['k1', 'k2'], 'name': 'test'},
params={'k1': 1, 'k2': 2}, params={'k1': 1, 'k2': 2},
payload={'A': 123}, payload={'A': 123},
query={'B': 456} query={'B': 456}
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, {'A': 123}) self.assertEqual(payload, {'A': 123})
@ -252,9 +224,9 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_3_without_payload_with_query(self): def test_calling_convention_3_without_payload_with_query(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test'}, {'args': ['k1', 'k2'], 'name': 'test'},
params={'k1': 1, 'k2': 2}, params={'k1': 1, 'k2': 2},
query={'B': 456} query={'B': 456}
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, None) self.assertEqual(payload, None)
@ -262,11 +234,11 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_3_with_positional_arguments_with_payload_with_query(self): def test_calling_convention_3_with_positional_arguments_with_payload_with_query(self):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test'}, {'args': ['k1', 'k2'], 'name': 'test'},
1, 1,
2, 2,
query={'B': 456}, query={'B': 456},
payload={'A': 123} payload={'A': 123}
) )
self.assertEqual(params, {'k1': 1, 'k2': 2}) self.assertEqual(params, {'k1': 1, 'k2': 2})
self.assertEqual(payload, {'A': 123}) self.assertEqual(payload, {'A': 123})
@ -286,12 +258,12 @@ class TestProcessArgs(ClientTest):
def test_calling_convention_3_with_pos_args_same_as_param_kwarg_dict_vals_with_payload_with_query(self): def test_calling_convention_3_with_pos_args_same_as_param_kwarg_dict_vals_with_payload_with_query(self):
with self.assertRaises(exc.TaskclusterFailure): with self.assertRaises(exc.TaskclusterFailure):
params, payload, query, _, _ = self.client._processArgs( params, payload, query, _, _ = self.client._processArgs(
{'args': ['k1', 'k2'], 'name': 'test'}, {'args': ['k1', 'k2'], 'name': 'test'},
1, 1,
2, 2,
params={'k1': 1, 'k2': 2}, params={'k1': 1, 'k2': 2},
query={'B': 456}, query={'B': 456},
payload={'A': 123} payload={'A': 123}
) )
@ -312,8 +284,6 @@ class ObjWithDotJson(object):
class TestMakeHttpRequest(ClientTest): class TestMakeHttpRequest(ClientTest):
apiPath = liburls.api(ClientTest.test_root_url, 'fake', 'v1', 'test')
def setUp(self): def setUp(self):
ClientTest.setUp(self) ClientTest.setUp(self)
@ -323,8 +293,8 @@ class TestMakeHttpRequest(ClientTest):
expected = {'test': 'works'} expected = {'test': 'works'}
p.return_value = ObjWithDotJson(200, expected) p.return_value = ObjWithDotJson(200, expected)
v = self.client._makeHttpRequest('GET', 'test', None) v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
p.assert_called_once_with('GET', self.apiPath, None, mock.ANY) p.assert_called_once_with('GET', 'http://www.example.com', None, mock.ANY)
self.assertEqual(expected, v) self.assertEqual(expected, v)
def test_success_first_try_payload(self): def test_success_first_try_payload(self):
@ -332,8 +302,8 @@ class TestMakeHttpRequest(ClientTest):
expected = {'test': 'works'} expected = {'test': 'works'}
p.return_value = ObjWithDotJson(200, expected) p.return_value = ObjWithDotJson(200, expected)
v = self.client._makeHttpRequest('GET', 'test', {'payload': 2}) v = self.client._makeHttpRequest('GET', 'http://www.example.com', {'payload': 2})
p.assert_called_once_with('GET', self.apiPath, p.assert_called_once_with('GET', 'http://www.example.com',
utils.dumpJson({'payload': 2}), mock.ANY) utils.dumpJson({'payload': 2}), mock.ANY)
self.assertEqual(expected, v) self.assertEqual(expected, v)
@ -348,10 +318,10 @@ class TestMakeHttpRequest(ClientTest):
ObjWithDotJson(200, expected) ObjWithDotJson(200, expected)
] ]
p.side_effect = sideEffect p.side_effect = sideEffect
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY) expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
for x in range(self.client.options['maxRetries'])] for x in range(self.client.options['maxRetries'])]
v = self.client._makeHttpRequest('GET', 'test', None) v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
p.assert_has_calls(expectedCalls) p.assert_has_calls(expectedCalls)
self.assertEqual(expected, v) self.assertEqual(expected, v)
@ -373,12 +343,12 @@ class TestMakeHttpRequest(ClientTest):
ObjWithDotJson(200, {'got this': 'wrong'}) ObjWithDotJson(200, {'got this': 'wrong'})
] ]
p.side_effect = sideEffect p.side_effect = sideEffect
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY) expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
for x in range(self.client.options['maxRetries'] + 1)] for x in range(self.client.options['maxRetries'] + 1)]
with self.assertRaises(exc.TaskclusterRestFailure): with self.assertRaises(exc.TaskclusterRestFailure):
try: try:
self.client._makeHttpRequest('GET', 'test', None) self.client._makeHttpRequest('GET', 'http://www.example.com', None)
except exc.TaskclusterRestFailure as err: except exc.TaskclusterRestFailure as err:
self.assertEqual('msg', str(err)) self.assertEqual('msg', str(err))
self.assertEqual(500, err.status_code) self.assertEqual(500, err.status_code)
@ -397,34 +367,43 @@ class TestMakeHttpRequest(ClientTest):
ObjWithDotJson(200, expected) ObjWithDotJson(200, expected)
] ]
p.side_effect = sideEffect p.side_effect = sideEffect
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY) expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
for x in range(self.client.options['maxRetries'])] for x in range(self.client.options['maxRetries'])]
v = self.client._makeHttpRequest('GET', 'test', None) v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
p.assert_has_calls(expectedCalls) p.assert_has_calls(expectedCalls)
self.assertEqual(expected, v) self.assertEqual(expected, v)
def test_failure_status_code(self): def test_failure_status_code(self):
with mock.patch.object(utils, 'makeSingleHttpRequest') as p: with mock.patch.object(utils, 'makeSingleHttpRequest') as p:
p.return_value = ObjWithDotJson(500, None) p.return_value = ObjWithDotJson(500, None)
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY) expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
for x in range(self.client.options['maxRetries'])] for x in range(self.client.options['maxRetries'])]
with self.assertRaises(exc.TaskclusterRestFailure): with self.assertRaises(exc.TaskclusterRestFailure):
self.client._makeHttpRequest('GET', 'test', None) self.client._makeHttpRequest('GET', 'http://www.example.com', None)
p.assert_has_calls(expectedCalls) p.assert_has_calls(expectedCalls)
def test_failure_connection_errors(self): def test_failure_connection_errors(self):
with mock.patch.object(utils, 'makeSingleHttpRequest') as p: with mock.patch.object(utils, 'makeSingleHttpRequest') as p:
p.side_effect = requests.exceptions.RequestException p.side_effect = requests.exceptions.RequestException
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY) expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
for x in range(self.client.options['maxRetries'])] for x in range(self.client.options['maxRetries'])]
with self.assertRaises(exc.TaskclusterConnectionError): with self.assertRaises(exc.TaskclusterConnectionError):
self.client._makeHttpRequest('GET', 'test', None) self.client._makeHttpRequest('GET', 'http://www.example.com', None)
p.assert_has_calls(expectedCalls) p.assert_has_calls(expectedCalls)
class TestOptions(ClientTest): class TestOptions(ClientTest):
def setUp(self):
ClientTest.setUp(self)
self.clientClass2 = subject.createApiClient('testApi', base.createApiRef())
self.client2 = self.clientClass2({'baseUrl': 'http://notlocalhost:5888/v2'})
def test_defaults_should_work(self):
self.assertEqual(self.client.options['baseUrl'], 'https://fake.taskcluster.net/v1')
self.assertEqual(self.client2.options['baseUrl'], 'http://notlocalhost:5888/v2')
def test_change_default_doesnt_change_previous_instances(self): def test_change_default_doesnt_change_previous_instances(self):
prevMaxRetries = subject._defaultConfig['maxRetries'] prevMaxRetries = subject._defaultConfig['maxRetries']
with mock.patch.dict(subject._defaultConfig, {'maxRetries': prevMaxRetries + 1}): with mock.patch.dict(subject._defaultConfig, {'maxRetries': prevMaxRetries + 1}):
@ -436,10 +415,7 @@ class TestOptions(ClientTest):
'clientId': u"\U0001F4A9", 'clientId': u"\U0001F4A9",
} }
with self.assertRaises(exc.TaskclusterAuthFailure): with self.assertRaises(exc.TaskclusterAuthFailure):
subject.Auth({ subject.Auth({'credentials': badCredentials})
'rootUrl': self.real_root_url,
'credentials': badCredentials,
})
class TestMakeApiCall(ClientTest): class TestMakeApiCall(ClientTest):
@ -542,13 +518,13 @@ class TestTopicExchange(ClientTest):
self.assertEqual(expected, actual['routingKeyPattern']) self.assertEqual(expected, actual['routingKeyPattern'])
def test_exchange(self): def test_exchange(self):
expected = 'exchange/taskcluster-fake/v1/topicExchange' expected = 'test/v1/topicExchange'
actual = self.client.topicName('') actual = self.client.topicName('')
self.assertEqual(expected, actual['exchange']) self.assertEqual(expected, actual['exchange'])
def test_exchange_trailing_slash(self): def test_exchange_trailing_slash(self):
self.client.options['exchangePrefix'] = 'exchange/taskcluster-fake2/v1/' self.client.options['exchangePrefix'] = 'test/v1/'
expected = 'exchange/taskcluster-fake2/v1/topicExchange' expected = 'test/v1/topicExchange'
actual = self.client.topicName('') actual = self.client.topicName('')
self.assertEqual(expected, actual['exchange']) self.assertEqual(expected, actual['exchange'])
@ -580,17 +556,18 @@ class TestTopicExchange(ClientTest):
class TestBuildUrl(ClientTest): class TestBuildUrl(ClientTest):
apiPath = liburls.api(ClientTest.test_root_url, 'fake', 'v1', 'two_args_no_input/arg0/arg1')
def test_build_url_positional(self): def test_build_url_positional(self):
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/arg1'
actual = self.client.buildUrl('two_args_no_input', 'arg0', 'arg1') actual = self.client.buildUrl('two_args_no_input', 'arg0', 'arg1')
self.assertEqual(self.apiPath, actual) self.assertEqual(expected, actual)
def test_build_url_keyword(self): def test_build_url_keyword(self):
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/arg1'
actual = self.client.buildUrl('two_args_no_input', arg0='arg0', arg1='arg1') actual = self.client.buildUrl('two_args_no_input', arg0='arg0', arg1='arg1')
self.assertEqual(self.apiPath, actual) self.assertEqual(expected, actual)
def test_build_url_query_string(self): def test_build_url_query_string(self):
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/arg1?qs0=1'
actual = self.client.buildUrl( actual = self.client.buildUrl(
'two_args_no_input', 'two_args_no_input',
params={ params={
@ -599,7 +576,7 @@ class TestBuildUrl(ClientTest):
}, },
query={'qs0': 1} query={'qs0': 1}
) )
self.assertEqual(self.apiPath + '?qs0=1', actual) self.assertEqual(expected, actual)
def test_fails_to_build_url_for_missing_method(self): def test_fails_to_build_url_for_missing_method(self):
with self.assertRaises(exc.TaskclusterFailure): with self.assertRaises(exc.TaskclusterFailure):
@ -612,17 +589,17 @@ class TestBuildUrl(ClientTest):
class TestBuildSignedUrl(ClientTest): class TestBuildSignedUrl(ClientTest):
apiPath = liburls.api(ClientTest.test_root_url, 'fake', 'v1', 'two_args_no_input/arg0/arg1')
def test_builds_surl_positional(self): def test_builds_surl_positional(self):
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/arg1?bewit=X'
actual = self.client.buildSignedUrl('two_args_no_input', 'arg0', 'arg1') actual = self.client.buildSignedUrl('two_args_no_input', 'arg0', 'arg1')
actual = re.sub('bewit=[^&]*', 'bewit=X', actual) actual = re.sub('bewit=[^&]*', 'bewit=X', actual)
self.assertEqual(self.apiPath + '?bewit=X', actual) self.assertEqual(expected, actual)
def test_builds_surl_keyword(self): def test_builds_surl_keyword(self):
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/arg1?bewit=X'
actual = self.client.buildSignedUrl('two_args_no_input', arg0='arg0', arg1='arg1') actual = self.client.buildSignedUrl('two_args_no_input', arg0='arg0', arg1='arg1')
actual = re.sub('bewit=[^&]*', 'bewit=X', actual) actual = re.sub('bewit=[^&]*', 'bewit=X', actual)
self.assertEqual(self.apiPath + '?bewit=X', actual) self.assertEqual(expected, actual)
class TestMockHttpCalls(ClientTest): class TestMockHttpCalls(ClientTest):
@ -645,30 +622,30 @@ class TestMockHttpCalls(ClientTest):
def test_no_args_no_input(self): def test_no_args_no_input(self):
with httmock.HTTMock(self.fakeSite): with httmock.HTTMock(self.fakeSite):
self.client.no_args_no_input() self.client.no_args_no_input()
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/v1/no_args_no_input') self.assertEqual(self.gotUrl, 'https://fake.taskcluster.net/v1/no_args_no_input')
def test_two_args_no_input(self): def test_two_args_no_input(self):
with httmock.HTTMock(self.fakeSite): with httmock.HTTMock(self.fakeSite):
self.client.two_args_no_input('1', '2') self.client.two_args_no_input('1', '2')
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/v1/two_args_no_input/1/2') self.assertEqual(self.gotUrl, 'https://fake.taskcluster.net/v1/two_args_no_input/1/2')
def test_no_args_with_input(self): def test_no_args_with_input(self):
with httmock.HTTMock(self.fakeSite): with httmock.HTTMock(self.fakeSite):
self.client.no_args_with_input({'x': 1}) self.client.no_args_with_input({'x': 1})
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/v1/no_args_with_input') self.assertEqual(self.gotUrl, 'https://fake.taskcluster.net/v1/no_args_with_input')
self.assertEqual(json.loads(self.gotRequest.body), {"x": 1}) self.assertEqual(json.loads(self.gotRequest.body), {"x": 1})
def test_no_args_with_empty_input(self): def test_no_args_with_empty_input(self):
with httmock.HTTMock(self.fakeSite): with httmock.HTTMock(self.fakeSite):
self.client.no_args_with_input({}) self.client.no_args_with_input({})
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/v1/no_args_with_input') self.assertEqual(self.gotUrl, 'https://fake.taskcluster.net/v1/no_args_with_input')
self.assertEqual(json.loads(self.gotRequest.body), {}) self.assertEqual(json.loads(self.gotRequest.body), {})
def test_two_args_with_input(self): def test_two_args_with_input(self):
with httmock.HTTMock(self.fakeSite): with httmock.HTTMock(self.fakeSite):
self.client.two_args_with_input('a', 'b', {'x': 1}) self.client.two_args_with_input('a', 'b', {'x': 1})
self.assertEqual(self.gotUrl, self.assertEqual(self.gotUrl,
'https://tc-tests.example.com/api/fake/v1/two_args_with_input/a/b') 'https://fake.taskcluster.net/v1/two_args_with_input/a/b')
self.assertEqual(json.loads(self.gotRequest.body), {"x": 1}) self.assertEqual(json.loads(self.gotRequest.body), {"x": 1})
def test_kwargs(self): def test_kwargs(self):
@ -676,7 +653,7 @@ class TestMockHttpCalls(ClientTest):
self.client.two_args_with_input( self.client.two_args_with_input(
{'x': 1}, arg0='a', arg1='b') {'x': 1}, arg0='a', arg1='b')
self.assertEqual(self.gotUrl, self.assertEqual(self.gotUrl,
'https://tc-tests.example.com/api/fake/v1/two_args_with_input/a/b') 'https://fake.taskcluster.net/v1/two_args_with_input/a/b')
self.assertEqual(json.loads(self.gotRequest.body), {"x": 1}) self.assertEqual(json.loads(self.gotRequest.body), {"x": 1})
@ -690,14 +667,14 @@ class TestAuthentication(base.TCTest):
@httmock.all_requests @httmock.all_requests
def auth_response(url, request): def auth_response(url, request):
self.assertEqual(urllib.parse.urlunsplit(url), self.assertEqual(urllib.parse.urlunsplit(url),
'https://tc-tests.example.com/api/auth/v1/clients/abc') 'https://auth.taskcluster.net/v1/clients/abc')
self.failIf('Authorization' in request.headers) self.failIf('Authorization' in request.headers)
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
content = {"clientId": "abc"} content = {"clientId": "abc"}
return httmock.response(200, content, headers, None, 5, request) return httmock.response(200, content, headers, None, 5, request)
with httmock.HTTMock(auth_response): with httmock.HTTMock(auth_response):
client = subject.Auth({"rootUrl": "https://tc-tests.example.com", "credentials": {}}) client = subject.Auth({"credentials": {}})
result = client.client('abc') result = client.client('abc')
self.assertEqual(result, {"clientId": "abc"}) self.assertEqual(result, {"clientId": "abc"})
@ -705,7 +682,6 @@ class TestAuthentication(base.TCTest):
"""we can call methods which require authentication with valid """we can call methods which require authentication with valid
permacreds""" permacreds"""
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'no-secret', 'accessToken': 'no-secret',
@ -719,7 +695,6 @@ class TestAuthentication(base.TCTest):
def test_permacred_simple_authorizedScopes(self): def test_permacred_simple_authorizedScopes(self):
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'no-secret', 'accessToken': 'no-secret',
@ -736,7 +711,6 @@ class TestAuthentication(base.TCTest):
def test_unicode_permacred_simple(self): def test_unicode_permacred_simple(self):
"""Unicode strings that encode to ASCII in credentials do not cause issues""" """Unicode strings that encode to ASCII in credentials do not cause issues"""
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': u'tester', 'clientId': u'tester',
'accessToken': u'no-secret', 'accessToken': u'no-secret',
@ -752,7 +726,6 @@ class TestAuthentication(base.TCTest):
"""Unicode strings that do not encode to ASCII in credentials cause issues""" """Unicode strings that do not encode to ASCII in credentials cause issues"""
with self.assertRaises(exc.TaskclusterAuthFailure): with self.assertRaises(exc.TaskclusterAuthFailure):
subject.Auth({ subject.Auth({
'rootUrl': self.test_root_url,
'credentials': { 'credentials': {
'clientId': u"\U0001F4A9", 'clientId': u"\U0001F4A9",
'accessToken': u"\U0001F4A9", 'accessToken': u"\U0001F4A9",
@ -762,7 +735,6 @@ class TestAuthentication(base.TCTest):
def test_permacred_insufficient_scopes(self): def test_permacred_insufficient_scopes(self):
"""A call with insufficient scopes results in an error""" """A call with insufficient scopes results in an error"""
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'no-secret', 'accessToken': 'no-secret',
@ -787,7 +759,6 @@ class TestAuthentication(base.TCTest):
['test:xyz'], ['test:xyz'],
) )
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
}) })
@ -807,7 +778,6 @@ class TestAuthentication(base.TCTest):
name='credName' name='credName'
) )
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
}) })
@ -826,7 +796,6 @@ class TestAuthentication(base.TCTest):
['test:xyz:*'], ['test:xyz:*'],
) )
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
'authorizedScopes': ['test:xyz:abc'], 'authorizedScopes': ['test:xyz:abc'],
}) })
@ -848,7 +817,6 @@ class TestAuthentication(base.TCTest):
name='credName' name='credName'
) )
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
'authorizedScopes': ['test:xyz:abc'], 'authorizedScopes': ['test:xyz:abc'],
}) })
@ -863,7 +831,6 @@ class TestAuthentication(base.TCTest):
def test_signed_url(self): def test_signed_url(self):
"""we can use a signed url built with the python client""" """we can use a signed url built with the python client"""
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'no-secret', 'accessToken': 'no-secret',
@ -881,7 +848,6 @@ class TestAuthentication(base.TCTest):
def test_signed_url_bad_credentials(self): def test_signed_url_bad_credentials(self):
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'wrong-secret', 'accessToken': 'wrong-secret',
@ -902,7 +868,6 @@ class TestAuthentication(base.TCTest):
['test:*'], ['test:*'],
) )
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
}) })
signedUrl = client.buildSignedUrl('testAuthenticateGet') signedUrl = client.buildSignedUrl('testAuthenticateGet')
@ -916,7 +881,6 @@ class TestAuthentication(base.TCTest):
def test_signed_url_authorizedScopes(self): def test_signed_url_authorizedScopes(self):
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': { 'credentials': {
'clientId': 'tester', 'clientId': 'tester',
'accessToken': 'no-secret', 'accessToken': 'no-secret',
@ -941,7 +905,6 @@ class TestAuthentication(base.TCTest):
['test:*'], ['test:*'],
) )
client = subject.Auth({ client = subject.Auth({
'rootUrl': self.real_root_url,
'credentials': tempCred, 'credentials': tempCred,
'authorizedScopes': ['test:authenticate-get'], 'authorizedScopes': ['test:authenticate-get'],
}) })

Просмотреть файл

@ -1,6 +1,5 @@
import datetime import datetime
import uuid import uuid
import os
import taskcluster.utils as subject import taskcluster.utils as subject
import dateutil.parser import dateutil.parser
@ -339,101 +338,3 @@ class TestIsExpired(TestCase):
} }
""") """)
self.assertEqual(isExpired, True) self.assertEqual(isExpired, True)
class TestFromEnv(TestCase):
def clear_env(self):
for v in 'ROOT_URL', 'CLIENT_ID', 'ACCESS_TOKEN', 'CERTIFICATE':
v = 'TASKCLUSTER_' + v
if v in os.environ:
del os.environ[v]
@mock.patch.dict(os.environ)
def test_empty(self):
self.clear_env()
self.assertEqual(subject.optionsFromEnvironment(), {})
@mock.patch.dict(os.environ)
def test_all(self):
os.environ['TASKCLUSTER_ROOT_URL'] = 'https://tc.example.com'
os.environ['TASKCLUSTER_CLIENT_ID'] = 'me'
os.environ['TASKCLUSTER_ACCESS_TOKEN'] = 'shave-and-a-haircut'
os.environ['TASKCLUSTER_CERTIFICATE'] = '{"bits":2}'
self.assertEqual(subject.optionsFromEnvironment(), {
'rootUrl': 'https://tc.example.com',
'credentials': {
'clientId': 'me',
'accessToken': 'shave-and-a-haircut',
'certificate': '{"bits":2}',
},
})
@mock.patch.dict(os.environ)
def test_cred_only(self):
os.environ['TASKCLUSTER_ACCESS_TOKEN'] = 'shave-and-a-haircut'
self.assertEqual(subject.optionsFromEnvironment(), {
'credentials': {
'accessToken': 'shave-and-a-haircut',
},
})
@mock.patch.dict(os.environ)
def test_rooturl_only(self):
os.environ['TASKCLUSTER_ROOT_URL'] = 'https://tc.example.com'
self.assertEqual(subject.optionsFromEnvironment(), {
'rootUrl': 'https://tc.example.com',
})
@mock.patch.dict(os.environ)
def test_default_rooturl(self):
os.environ['TASKCLUSTER_CLIENT_ID'] = 'me'
os.environ['TASKCLUSTER_ACCESS_TOKEN'] = 'shave-and-a-haircut'
os.environ['TASKCLUSTER_CERTIFICATE'] = '{"bits":2}'
self.assertEqual(
subject.optionsFromEnvironment({'rootUrl': 'https://other.example.com'}), {
'rootUrl': 'https://other.example.com',
'credentials': {
'clientId': 'me',
'accessToken': 'shave-and-a-haircut',
'certificate': '{"bits":2}',
},
})
@mock.patch.dict(os.environ)
def test_default_rooturl_overridden(self):
os.environ['TASKCLUSTER_ROOT_URL'] = 'https://tc.example.com'
self.assertEqual(
subject.optionsFromEnvironment({'rootUrl': 'https://other.example.com'}),
{'rootUrl': 'https://tc.example.com'})
@mock.patch.dict(os.environ)
def test_default_creds(self):
os.environ['TASKCLUSTER_ROOT_URL'] = 'https://tc.example.com'
os.environ['TASKCLUSTER_ACCESS_TOKEN'] = 'shave-and-a-haircut'
os.environ['TASKCLUSTER_CERTIFICATE'] = '{"bits":2}'
self.assertEqual(
subject.optionsFromEnvironment({'credentials': {'clientId': 'them'}}), {
'rootUrl': 'https://tc.example.com',
'credentials': {
'clientId': 'them',
'accessToken': 'shave-and-a-haircut',
'certificate': '{"bits":2}',
},
})
@mock.patch.dict(os.environ)
def test_default_creds_overridden(self):
os.environ['TASKCLUSTER_ROOT_URL'] = 'https://tc.example.com'
os.environ['TASKCLUSTER_CLIENT_ID'] = 'me'
os.environ['TASKCLUSTER_ACCESS_TOKEN'] = 'shave-and-a-haircut'
os.environ['TASKCLUSTER_CERTIFICATE'] = '{"bits":2}'
self.assertEqual(
subject.optionsFromEnvironment({'credentials': {'clientId': 'them'}}), {
'rootUrl': 'https://tc.example.com',
'credentials': {
'clientId': 'me',
'accessToken': 'shave-and-a-haircut',
'certificate': '{"bits":2}',
},
})