зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
0e57cd7404
Коммит
c152ccec1d
|
@ -10,8 +10,6 @@ import os
|
|||
import re
|
||||
import requests
|
||||
from taskcluster.notify import Notify
|
||||
from taskcluster import optionsFromEnvironment
|
||||
from taskgraph.util.taskcluster import get_root_url
|
||||
from operator import itemgetter
|
||||
|
||||
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)
|
||||
|
||||
notify_options = optionsFromEnvironment({'rootUrl': get_root_url()})
|
||||
notify_options = {}
|
||||
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)
|
||||
for address in addresses:
|
||||
notify.email({
|
||||
|
|
|
@ -33,7 +33,8 @@ then
|
|||
test "${AWS_BUCKET_NAME}"
|
||||
|
||||
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}")
|
||||
AWS_ACCESS_KEY_ID=$(echo "${AUTH}" | jq -r '.credentials.accessKeyId')
|
||||
AWS_SECRET_ACCESS_KEY=$(echo "${AUTH}" | jq -r '.credentials.secretAccessKey')
|
||||
|
|
|
@ -68,7 +68,8 @@ fi
|
|||
if [ -n "${ARC_SECRET}" ] && getent hosts taskcluster
|
||||
then
|
||||
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}")
|
||||
TOKEN=$(echo "${SECRET}" | jq -r '.secret.token')
|
||||
elif [ -n "${ARC_TOKEN}" ] # Allow for local testing.
|
||||
|
|
|
@ -36,7 +36,8 @@ fi
|
|||
if [ -n "${ARC_SECRET}" ] && getent hosts taskcluster
|
||||
then
|
||||
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}")
|
||||
TOKEN=$(echo "${SECRET}" | jq -r '.secret.token')
|
||||
elif [ -n "${ARC_TOKEN}" ] # Allow for local testing.
|
||||
|
|
|
@ -396,7 +396,8 @@ def command_task_artifacts(args):
|
|||
task=fetch['task'], artifact=fetch['artifact'])
|
||||
url = api(root_url, 'queue', 'v1', path)
|
||||
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'],
|
||||
task=fetch['task'],
|
||||
artifact=fetch['artifact'])
|
||||
|
|
|
@ -21,6 +21,7 @@ from taskgraph.util.attributes import TRUNK_PROJECTS
|
|||
from taskgraph.util.hash import hash_path
|
||||
from taskgraph.util.treeherder import split_symbol
|
||||
from taskgraph.transforms.base import TransformSequence
|
||||
from taskgraph.util.taskcluster import get_root_url
|
||||
from taskgraph.util.schema import (
|
||||
validate_schema,
|
||||
Schema,
|
||||
|
@ -492,6 +493,11 @@ def build_docker_worker_payload(config, task, task_def):
|
|||
else:
|
||||
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 = {}
|
||||
|
||||
if worker.get('relengapi-proxy'):
|
||||
|
@ -499,6 +505,7 @@ def build_docker_worker_payload(config, task, task_def):
|
|||
|
||||
if worker.get('taskcluster-proxy'):
|
||||
features['taskclusterProxy'] = True
|
||||
worker['env']['TASKCLUSTER_PROXY_URL'] = 'http://taskcluster/'
|
||||
|
||||
if worker.get('allow-ptrace'):
|
||||
features['allowPtrace'] = True
|
||||
|
@ -523,6 +530,11 @@ def build_docker_worker_payload(config, task, task_def):
|
|||
else:
|
||||
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 = {}
|
||||
|
||||
for lo in 'audio', 'video':
|
||||
|
@ -755,6 +767,11 @@ def build_generic_worker_payload(config, task, task_def):
|
|||
|
||||
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'):
|
||||
env['USE_SCCACHE'] = '1'
|
||||
# Disable sccache idle shutdown.
|
||||
|
@ -809,6 +826,9 @@ def build_generic_worker_payload(config, task, task_def):
|
|||
|
||||
if worker.get('taskcluster-proxy'):
|
||||
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):
|
||||
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):
|
||||
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: {
|
||||
'name': artifact['name'],
|
||||
'path': artifact['path'],
|
||||
|
|
|
@ -31,23 +31,10 @@ CONCURRENCY = 50
|
|||
|
||||
|
||||
@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
|
||||
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
|
||||
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')
|
||||
|
||||
defualt that points to the production deployment of Taskcluster."""
|
||||
if 'TASKCLUSTER_ROOT_URL' not in os.environ:
|
||||
if 'TASK_ID' in os.environ:
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
|
@ -204,7 +195,11 @@ def parse_time(timestamp):
|
|||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -245,13 +240,17 @@ def rerun_task(task_id):
|
|||
def get_current_scopes():
|
||||
"""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."""
|
||||
auth_url = liburls.api(get_root_url(use_proxy=True), 'auth', 'v1', 'scopes/current')
|
||||
resp = _do_request(auth_url)
|
||||
# Until bug 1460015 is finished, use the old baseUrl style of proxy URL
|
||||
resp = _do_request(os.environ['TASKCLUSTER_PROXY_URL'] + '/auth/v1/scopes/current')
|
||||
return resp.json().get("scopes", [])
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -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):
|
||||
"""Sends an email using the notify service"""
|
||||
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={
|
||||
'address': address,
|
||||
'subject': subject,
|
||||
|
|
|
@ -12,7 +12,7 @@ python-hglib==2.4
|
|||
redo==2.0.2
|
||||
requests==2.9.1
|
||||
six==1.10.0
|
||||
taskcluster==6.0.0
|
||||
taskcluster==4.0.1
|
||||
taskcluster-urls==11.0.0
|
||||
virtualenv==15.2.0
|
||||
voluptuous==0.11.5
|
||||
|
|
|
@ -104,10 +104,10 @@ taskcluster-urls==11.0.0 \
|
|||
--hash=sha256:18dcaa9c2412d34ff6c78faca33f0dd8f2384e3f00a98d5832c62d6d664741f0 \
|
||||
--hash=sha256:2aceab7cf5b1948bc197f2e5e50c371aa48181ccd490b8bada00f1e3baf0c5cc \
|
||||
--hash=sha256:74bd2110b5daaebcec5e1d287bf137b61cb8cf6b2d8f5f2b74183e32bc4e7c87
|
||||
taskcluster==6.0.0 \
|
||||
--hash=sha256:48ecd4898c7928deddfb34cb1cfe2b2505c68416e6c503f8a7f3dd0572425e96 \
|
||||
--hash=sha256:6d5cf7bdbc09dc48b2d376b418b95c1c157a2d359c4b6b231c1fb14a323c0cc5 \
|
||||
--hash=sha256:e409fce7a72808e4f87dc7baca7a79d8b64d5c5045264b9e197c120cc40e219b
|
||||
taskcluster==4.0.1 \
|
||||
--hash=sha256:27256511044346ac71a495d3c636f2add95c102b9b09f90d6fb1ea3e9949d311 \
|
||||
--hash=sha256:99dd90bc1c566968868c8b07ede32f8e031cbccd52c7195a61e802679d461447 \
|
||||
--hash=sha256:d0360063c1a3fcaaa514bb31c03954ba573d2b671df40a2ecfdfd9339cc8e93e
|
||||
virtualenv-clone==0.3.0 \
|
||||
--hash=sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7 \
|
||||
--hash=sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8 \
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: taskcluster
|
||||
Version: 6.0.0
|
||||
Version: 4.0.1
|
||||
Summary: Python client for Taskcluster
|
||||
Home-page: https://github.com/taskcluster/taskcluster-client.py
|
||||
Author: John Ford
|
||||
|
|
|
@ -70,7 +70,7 @@ python2-compatible and operate synchronously.
|
|||
|
||||
|
||||
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
|
||||
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
|
||||
|
@ -101,10 +101,7 @@ Here's a slide deck for an [introduction to async python](https://gitpitch.com/e
|
|||
|
||||
```python
|
||||
import taskcluster
|
||||
index = taskcluster.Index({
|
||||
'rootUrl': 'https://tc.example.com',
|
||||
'credentials': {'clientId': 'id', 'accessToken': 'accessToken'},
|
||||
})
|
||||
index = taskcluster.Index({'credentials': {'clientId': 'id', 'accessToken': 'accessToken'}})
|
||||
index.ping()
|
||||
```
|
||||
|
||||
|
@ -122,34 +119,18 @@ Here's a slide deck for an [introduction to async python](https://gitpitch.com/e
|
|||
|
||||
```python
|
||||
from taskcluster import client
|
||||
qEvt = client.QueueEvents({rootUrl: 'https://tc.example.com'})
|
||||
qEvt = client.QueueEvents()
|
||||
# The following calls are equivalent
|
||||
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
|
||||
There are two ways to accomplish pagination easily with the python client. The first is
|
||||
to implement pagination in your code:
|
||||
```python
|
||||
import taskcluster
|
||||
queue = taskcluster.Queue({'rootUrl': 'https://tc.example.com'})
|
||||
queue = taskcluster.Queue()
|
||||
i = 0
|
||||
tasks = 0
|
||||
outcome = queue.listTaskGroup('JzTGxwxhQ76_Tt1dxkaG5g')
|
||||
|
@ -172,7 +153,7 @@ built and then counted:
|
|||
|
||||
```python
|
||||
import taskcluster
|
||||
queue = taskcluster.Queue({'rootUrl': 'https://tc.example.com'})
|
||||
queue = taskcluster.Queue()
|
||||
|
||||
responses = []
|
||||
|
||||
|
@ -1030,9 +1011,9 @@ await asyncAuth.testAuthenticateGet() # -> result
|
|||
import taskcluster
|
||||
authEvents = taskcluster.AuthEvents(options)
|
||||
```
|
||||
The auth service is responsible for storing credentials, managing
|
||||
assignment of scopes, and validation of request signatures from other
|
||||
services.
|
||||
The auth service, typically available at `auth.taskcluster.net`
|
||||
is responsible for storing credentials, managing assignment of scopes,
|
||||
and validation of request signatures from other services.
|
||||
|
||||
These exchanges provides notifications when credentials or roles are
|
||||
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)
|
||||
```
|
||||
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
|
||||
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
|
||||
# Sync calls
|
||||
|
@ -1541,7 +1510,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [input schema](v1/run-instance-request.json#)
|
||||
Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1579,7 +1548,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](v1/worker-type-resources.json#)
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1599,7 +1568,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](v1/health.json#)
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/health.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1619,7 +1588,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](v1/errors.json#)
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/errors.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1639,7 +1608,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](v1/worker-type-state.json#)
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1659,7 +1628,7 @@ Takes the following arguments:
|
|||
|
||||
* `name`
|
||||
|
||||
Required [input schema](v1/create-key-pair.json#)
|
||||
Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1711,7 +1680,7 @@ await asyncEC2Manager.terminateInstance(region='value', instanceId='value') # ->
|
|||
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
|
||||
# Sync calls
|
||||
|
@ -1724,9 +1693,9 @@ await asyncEC2Manager.getPrices() # -> result
|
|||
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
|
||||
# Sync calls
|
||||
|
@ -1739,7 +1708,7 @@ await asyncEC2Manager.getSpecificPrices(payload) # -> result
|
|||
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
|
||||
# Sync calls
|
||||
|
@ -1752,7 +1721,7 @@ await asyncEC2Manager.getHealth() # -> result
|
|||
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
|
||||
# Sync calls
|
||||
|
@ -1852,6 +1821,29 @@ eC2Manager.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)
|
||||
asyncGithub = taskcluster.aio.Github(options, session=session)
|
||||
```
|
||||
The github service is responsible for creating tasks in reposnse
|
||||
to GitHub events, and posting results to the GitHub UI.
|
||||
The github service, typically available at
|
||||
`github.taskcluster.net`, is responsible for publishing pulse
|
||||
messages in response to GitHub events.
|
||||
|
||||
This document describes the API end-point for consuming GitHub
|
||||
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.
|
||||
* `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
|
||||
|
||||
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.
|
||||
#### Ping Server
|
||||
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
|
||||
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`
|
||||
Given an OIDC `access_token` from a trusted OpenID provider, return a
|
||||
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
|
||||
: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
|
||||
appropriate to the user. Note that the resulting credentials may or may
|
||||
not include a `certificate` property. Callers should be prepared for either
|
||||
|
@ -2677,7 +2652,7 @@ Takes the following arguments:
|
|||
|
||||
* `provider`
|
||||
|
||||
Required [output schema](v1/oidc-credentials-response.json#)
|
||||
Required [output schema](http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -2688,6 +2663,18 @@ await asyncLogin.oidcCredentials(provider) # -> 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`
|
||||
```python
|
||||
import asyncio # Only for async
|
||||
|
@ -2892,7 +2774,8 @@ loop = asyncio.get_event_loop()
|
|||
session = taskcluster.aio.createSession(loop=loop)
|
||||
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.
|
||||
|
||||
This document describes the API end-point for publishing the pulse
|
||||
|
|
|
@ -7,7 +7,7 @@ import sys
|
|||
# The VERSION variable is automagically changed
|
||||
# by release.sh. Make sure you understand how
|
||||
# that script works if you want to change this
|
||||
VERSION = '6.0.0'
|
||||
VERSION = '4.0.1'
|
||||
|
||||
tests_require = [
|
||||
'nose==1.3.7',
|
||||
|
@ -30,7 +30,6 @@ install_requires = [
|
|||
'requests>=2.4.3,<3',
|
||||
'mohawk>=0.3.4,<0.4',
|
||||
'slugid>=1.0.7,<2',
|
||||
'taskcluster-urls>=10.1.0,<12',
|
||||
'six>=1.10.0,<2',
|
||||
]
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ from .hooks import Hooks # NOQA
|
|||
from .index import Index # NOQA
|
||||
from .login import Login # NOQA
|
||||
from .notify import Notify # NOQA
|
||||
from .pulse import Pulse # NOQA
|
||||
from .purgecache import PurgeCache # NOQA
|
||||
from .purgecacheevents import PurgeCacheEvents # NOQA
|
||||
from .queue import Queue # NOQA
|
||||
|
|
|
@ -9,7 +9,6 @@ from .hooks import Hooks # NOQA
|
|||
from .index import Index # NOQA
|
||||
from .login import Login # NOQA
|
||||
from .notify import Notify # NOQA
|
||||
from .pulse import Pulse # NOQA
|
||||
from .purgecache import PurgeCache # NOQA
|
||||
from .purgecacheevents import PurgeCacheEvents # 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
|
||||
of doing an HTTP request to another method."""
|
||||
|
||||
url = self._constructUrl(route)
|
||||
url = self._joinBaseUrlAndRoute(route)
|
||||
log.debug('Full URL used is: %s', url)
|
||||
|
||||
hawkExt = self.makeHawkExt()
|
||||
|
@ -221,7 +221,7 @@ class AsyncBaseClient(BaseClient):
|
|||
try:
|
||||
await response.release()
|
||||
return await response.json()
|
||||
except (ValueError, aiohttp.client_exceptions.ContentTypeError):
|
||||
except ValueError:
|
||||
return {"response": response}
|
||||
|
||||
# This code-path should be unreachable
|
||||
|
@ -239,8 +239,6 @@ class AsyncBaseClient(BaseClient):
|
|||
|
||||
|
||||
def createApiClient(name, api):
|
||||
api = api['reference']
|
||||
|
||||
attributes = dict(
|
||||
name=name,
|
||||
__doc__=api.get('description'),
|
||||
|
@ -248,22 +246,12 @@ def createApiClient(name, api):
|
|||
funcinfo={},
|
||||
)
|
||||
|
||||
# apply a default for apiVersion; this can be removed when all services
|
||||
# have apiVersion
|
||||
if 'apiVersion' not in api:
|
||||
api['apiVersion'] = 'v1'
|
||||
|
||||
copiedOptions = ('exchangePrefix',)
|
||||
copiedOptions = ('baseUrl', 'exchangePrefix')
|
||||
for opt in copiedOptions:
|
||||
if opt in api:
|
||||
attributes['classOptions'][opt] = api[opt]
|
||||
if opt in api['reference']:
|
||||
attributes['classOptions'][opt] = api['reference'][opt]
|
||||
|
||||
copiedProperties = ('serviceName', 'apiVersion')
|
||||
for opt in copiedProperties:
|
||||
if opt in api:
|
||||
attributes[opt] = api[opt]
|
||||
|
||||
for entry in api['entries']:
|
||||
for entry in api['reference']['entries']:
|
||||
if entry['type'] == 'function':
|
||||
def addApiCall(e):
|
||||
async def apiCall(self, *args, **kwargs):
|
||||
|
|
|
@ -111,6 +111,6 @@ async def putFile(filename, url, contentType, session=None):
|
|||
with open(filename, 'rb') as f:
|
||||
contentLength = os.fstat(f.fileno()).st_size
|
||||
return await makeHttpRequest('put', url, f, headers={
|
||||
'Content-Length': str(contentLength),
|
||||
'Content-Length': contentLength,
|
||||
'Content-Type': contentType,
|
||||
}, session=session)
|
||||
|
|
|
@ -55,9 +55,8 @@ class Auth(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://auth.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'auth'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -13,9 +13,9 @@ _defaultConfig = config
|
|||
|
||||
class AuthEvents(AsyncBaseClient):
|
||||
"""
|
||||
The auth service is responsible for storing credentials, managing
|
||||
assignment of scopes, and validation of request signatures from other
|
||||
services.
|
||||
The auth service, typically available at `auth.taskcluster.net`
|
||||
is responsible for storing credentials, managing assignment of scopes,
|
||||
and validation of request signatures from other services.
|
||||
|
||||
These exchanges provides notifications when credentials or roles are
|
||||
updated. This is mostly so that multiple instances of the auth service
|
||||
|
@ -24,10 +24,8 @@ class AuthEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/"
|
||||
}
|
||||
serviceName = 'auth'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def clientCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -44,9 +44,8 @@ class AwsProvisioner(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://aws-provisioner.taskcluster.net/v1"
|
||||
}
|
||||
serviceName = 'aws-provisioner'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def listWorkerTypeSummaries(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -17,9 +17,8 @@ class AwsProvisionerEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/"
|
||||
}
|
||||
apiVersion = 'v1'
|
||||
|
||||
def workerTypeCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -17,21 +17,8 @@ class EC2Manager(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -39,7 +26,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -52,7 +39,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -76,7 +63,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -89,7 +76,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -102,7 +89,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -115,7 +102,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -128,7 +115,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -163,7 +150,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -176,9 +163,9 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -191,7 +178,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -204,7 +191,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -302,6 +289,29 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
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 = {
|
||||
"allState": {
|
||||
'args': [],
|
||||
|
@ -317,6 +327,13 @@ class EC2Manager(AsyncBaseClient):
|
|||
'route': '/internal/ami-usage',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"apiReference": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'apiReference',
|
||||
'route': '/internal/api-reference',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"dbpoolStats": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
|
@ -333,7 +350,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
},
|
||||
"ensureKeyPair": {
|
||||
'args': ['name'],
|
||||
'input': 'v1/create-key-pair.json#',
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#',
|
||||
'method': 'get',
|
||||
'name': 'ensureKeyPair',
|
||||
'route': '/key-pairs/<name>',
|
||||
|
@ -343,7 +360,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getHealth',
|
||||
'output': 'v1/health.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'route': '/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -351,7 +368,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getPrices',
|
||||
'output': 'v1/prices.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -359,16 +376,16 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getRecentErrors',
|
||||
'output': 'v1/errors.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'route': '/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"getSpecificPrices": {
|
||||
'args': [],
|
||||
'input': 'v1/prices-request.json#',
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#',
|
||||
'method': 'post',
|
||||
'name': 'getSpecificPrices',
|
||||
'output': 'v1/prices.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -376,7 +393,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listWorkerTypes',
|
||||
'output': 'v1/list-worker-types.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#',
|
||||
'route': '/worker-types',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -410,7 +427,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
},
|
||||
"runInstance": {
|
||||
'args': ['workerType'],
|
||||
'input': 'v1/run-instance-request.json#',
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#',
|
||||
'method': 'put',
|
||||
'name': 'runInstance',
|
||||
'route': '/worker-types/<workerType>/instance',
|
||||
|
@ -441,7 +458,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeErrors',
|
||||
'output': 'v1/errors.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'route': '/worker-types/<workerType>/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -449,7 +466,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeHealth',
|
||||
'output': 'v1/health.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'route': '/worker-types/<workerType>/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -457,7 +474,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'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',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -465,7 +482,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'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',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -13,8 +13,9 @@ _defaultConfig = config
|
|||
|
||||
class Github(AsyncBaseClient):
|
||||
"""
|
||||
The github service is responsible for creating tasks in reposnse
|
||||
to GitHub events, and posting results to the GitHub UI.
|
||||
The github service, typically available at
|
||||
`github.taskcluster.net`, is responsible for publishing pulse
|
||||
messages in response to GitHub events.
|
||||
|
||||
This document describes the API end-point for consuming GitHub
|
||||
web hooks, as well as some useful consumer APIs.
|
||||
|
@ -24,9 +25,8 @@ class Github(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://github.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,10 +22,8 @@ class GithubEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/"
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def pullRequest(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -150,43 +148,6 @@ class GithubEvents(AsyncBaseClient):
|
|||
}
|
||||
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 = {
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://hooks.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'hooks'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -108,9 +108,8 @@ class Index(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://index.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'index'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -18,21 +18,8 @@ class Login(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -50,7 +37,7 @@ class Login(AsyncBaseClient):
|
|||
```
|
||||
|
||||
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
|
||||
appropriate to the user. Note that the resulting credentials may or may
|
||||
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
|
||||
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``
|
||||
"""
|
||||
|
||||
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 = {
|
||||
"oidcCredentials": {
|
||||
'args': ['provider'],
|
||||
'method': 'get',
|
||||
'name': 'oidcCredentials',
|
||||
'output': 'v1/oidc-credentials-response.json#',
|
||||
'output': 'http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json',
|
||||
'route': '/oidc-credentials/<provider>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -19,9 +19,8 @@ class Notify(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://notify.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'notify'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,21 +22,21 @@ class Pulse(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
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.
|
||||
This endpoint is used to check that the service is up.
|
||||
Get an overview of the Rabbit cluster.
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -46,9 +46,9 @@ class Pulse(AsyncBaseClient):
|
|||
|
||||
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.
|
||||
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``
|
||||
"""
|
||||
|
@ -62,7 +62,7 @@ class Pulse(AsyncBaseClient):
|
|||
Get public information about a single namespace. This is the same information
|
||||
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``
|
||||
"""
|
||||
|
@ -73,35 +73,43 @@ class Pulse(AsyncBaseClient):
|
|||
"""
|
||||
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`.
|
||||
Claim a namespace, returning a username and password with access to that
|
||||
namespace good for a short time. Clients should call this endpoint again
|
||||
at the re-claim time given in the response, as the password will be rotated
|
||||
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`,
|
||||
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 `expires` and `contact` properties can be updated at any time in a reclaim
|
||||
operation.
|
||||
|
||||
The specified `expires` time updates any existing expiration times. Connections
|
||||
for expired namespaces will be terminated.
|
||||
This method takes input: ``http://schemas.taskcluster.net/pulse/v1/namespace-request.json``
|
||||
|
||||
This method takes input: ``v1/namespace-request.json#``
|
||||
|
||||
This method gives output: ``v1/namespace-response.json#``
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace-response.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
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 = {
|
||||
"claimNamespace": {
|
||||
'args': ['namespace'],
|
||||
'input': 'v1/namespace-request.json#',
|
||||
'input': 'http://schemas.taskcluster.net/pulse/v1/namespace-request.json',
|
||||
'method': 'post',
|
||||
'name': 'claimNamespace',
|
||||
'output': 'v1/namespace-response.json#',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace-response.json',
|
||||
'route': '/namespace/<namespace>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -109,8 +117,8 @@ class Pulse(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listNamespaces',
|
||||
'output': 'v1/list-namespaces-response.json#',
|
||||
'query': ['limit', 'continuationToken'],
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json',
|
||||
'query': ['limit', 'continuation'],
|
||||
'route': '/namespaces',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -118,10 +126,18 @@ class Pulse(AsyncBaseClient):
|
|||
'args': ['namespace'],
|
||||
'method': 'get',
|
||||
'name': 'namespace',
|
||||
'output': 'v1/namespace.json#',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace.json',
|
||||
'route': '/namespace/<namespace>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"overview": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'overview',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json',
|
||||
'route': '/overview',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"ping": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
|
|
|
@ -13,7 +13,8 @@ _defaultConfig = config
|
|||
|
||||
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.
|
||||
|
||||
This document describes the API end-point for publishing the pulse
|
||||
|
@ -21,9 +22,8 @@ class PurgeCache(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://purge-cache.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'purge-cache'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,10 +22,8 @@ class PurgeCacheEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-purge-cache/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-purge-cache/v1/"
|
||||
}
|
||||
serviceName = 'purge-cache'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def purgeCache(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -25,9 +25,8 @@ class Queue(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://queue.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -63,10 +63,8 @@ class QueueEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/"
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def taskDefined(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,9 +23,8 @@ class Secrets(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://secrets.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'secrets'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,10 +23,8 @@ class TreeherderEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/"
|
||||
}
|
||||
serviceName = 'treeherder'
|
||||
apiVersion = 'v1'
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
@ -61,7 +59,7 @@ class TreeherderEvents(AsyncBaseClient):
|
|||
'name': 'reserved',
|
||||
},
|
||||
],
|
||||
'schema': 'v1/pulse-job.json#',
|
||||
'schema': 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#',
|
||||
}
|
||||
return self._makeTopicExchange(ref, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -55,9 +55,8 @@ class Auth(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://auth.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'auth'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -13,9 +13,9 @@ _defaultConfig = config
|
|||
|
||||
class AuthEvents(BaseClient):
|
||||
"""
|
||||
The auth service is responsible for storing credentials, managing
|
||||
assignment of scopes, and validation of request signatures from other
|
||||
services.
|
||||
The auth service, typically available at `auth.taskcluster.net`
|
||||
is responsible for storing credentials, managing assignment of scopes,
|
||||
and validation of request signatures from other services.
|
||||
|
||||
These exchanges provides notifications when credentials or roles are
|
||||
updated. This is mostly so that multiple instances of the auth service
|
||||
|
@ -24,10 +24,8 @@ class AuthEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/"
|
||||
}
|
||||
serviceName = 'auth'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def clientCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -44,9 +44,8 @@ class AwsProvisioner(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://aws-provisioner.taskcluster.net/v1"
|
||||
}
|
||||
serviceName = 'aws-provisioner'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def listWorkerTypeSummaries(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -17,9 +17,8 @@ class AwsProvisionerEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/"
|
||||
}
|
||||
apiVersion = 'v1'
|
||||
|
||||
def workerTypeCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import copy
|
||||
|
@ -20,7 +21,6 @@ import mohawk.bewit
|
|||
|
||||
import taskcluster.exceptions as exceptions
|
||||
import taskcluster.utils as utils
|
||||
import taskcluster_urls as liburls
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,11 +28,10 @@ log = logging.getLogger(__name__)
|
|||
# Default configuration
|
||||
_defaultConfig = config = {
|
||||
'credentials': {
|
||||
'clientId': None,
|
||||
'accessToken': None,
|
||||
'certificate': None,
|
||||
'clientId': os.environ.get('TASKCLUSTER_CLIENT_ID'),
|
||||
'accessToken': os.environ.get('TASKCLUSTER_ACCESS_TOKEN'),
|
||||
'certificate': os.environ.get('TASKCLUSTER_CERTIFICATE'),
|
||||
},
|
||||
'rootUrl': None,
|
||||
'maxRetries': 5,
|
||||
'signedUrlExpiration': 15 * 60,
|
||||
}
|
||||
|
@ -53,14 +52,10 @@ class BaseClient(object):
|
|||
"""
|
||||
|
||||
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.update(_defaultConfig)
|
||||
if options:
|
||||
o.update(options)
|
||||
if not o.get('rootUrl'):
|
||||
raise exceptions.TaskclusterFailure('rootUrl option is required')
|
||||
|
||||
credentials = o.get('credentials')
|
||||
if credentials:
|
||||
|
@ -72,7 +67,6 @@ class BaseClient(object):
|
|||
except:
|
||||
s = '%s (%s) must be unicode encodable' % (x, credentials[x])
|
||||
raise exceptions.TaskclusterAuthFailure(s)
|
||||
|
||||
self.options = o
|
||||
if 'credentials' in o:
|
||||
log.debug('credentials key scrubbed from logging output')
|
||||
|
@ -174,7 +168,7 @@ class BaseClient(object):
|
|||
route = self._subArgsInRoute(entry, routeParams)
|
||||
if 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):
|
||||
""" Build a signed URL. This URL contains the credentials needed to access
|
||||
|
@ -243,14 +237,11 @@ class BaseClient(object):
|
|||
u.fragment,
|
||||
))
|
||||
|
||||
def _constructUrl(self, route):
|
||||
"""Construct a URL for the given route on this service, based on the
|
||||
rootUrl"""
|
||||
return liburls.api(
|
||||
self.options['rootUrl'],
|
||||
self.serviceName,
|
||||
self.apiVersion,
|
||||
route.rstrip('/'))
|
||||
def _joinBaseUrlAndRoute(self, route):
|
||||
return urllib.parse.urljoin(
|
||||
'{}/'.format(self.options['baseUrl'].rstrip('/')),
|
||||
route.lstrip('/')
|
||||
)
|
||||
|
||||
def _makeApiCall(self, entry, *args, **kwargs):
|
||||
""" 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
|
||||
of doing an HTTP request to another method."""
|
||||
|
||||
url = self._constructUrl(route)
|
||||
url = self._joinBaseUrlAndRoute(route)
|
||||
log.debug('Full URL used is: %s', url)
|
||||
|
||||
hawkExt = self.makeHawkExt()
|
||||
|
@ -554,8 +545,6 @@ class BaseClient(object):
|
|||
|
||||
|
||||
def createApiClient(name, api):
|
||||
api = api['reference']
|
||||
|
||||
attributes = dict(
|
||||
name=name,
|
||||
__doc__=api.get('description'),
|
||||
|
@ -563,22 +552,12 @@ def createApiClient(name, api):
|
|||
funcinfo={},
|
||||
)
|
||||
|
||||
# apply a default for apiVersion; this can be removed when all services
|
||||
# have apiVersion
|
||||
if 'apiVersion' not in api:
|
||||
api['apiVersion'] = 'v1'
|
||||
|
||||
copiedOptions = ('exchangePrefix',)
|
||||
copiedOptions = ('baseUrl', 'exchangePrefix')
|
||||
for opt in copiedOptions:
|
||||
if opt in api:
|
||||
attributes['classOptions'][opt] = api[opt]
|
||||
if opt in api['reference']:
|
||||
attributes['classOptions'][opt] = api['reference'][opt]
|
||||
|
||||
copiedProperties = ('serviceName', 'apiVersion')
|
||||
for opt in copiedProperties:
|
||||
if opt in api:
|
||||
attributes[opt] = api[opt]
|
||||
|
||||
for entry in api['entries']:
|
||||
for entry in api['reference']['entries']:
|
||||
if entry['type'] == 'function':
|
||||
def addApiCall(e):
|
||||
def apiCall(self, *args, **kwargs):
|
||||
|
|
|
@ -17,21 +17,8 @@ class EC2Manager(BaseClient):
|
|||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -39,7 +26,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -52,7 +39,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -76,7 +63,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -89,7 +76,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -102,7 +89,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -115,7 +102,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -128,7 +115,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -163,7 +150,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -176,9 +163,9 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -191,7 +178,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -204,7 +191,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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``
|
||||
"""
|
||||
|
@ -302,6 +289,29 @@ class EC2Manager(BaseClient):
|
|||
|
||||
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 = {
|
||||
"allState": {
|
||||
'args': [],
|
||||
|
@ -317,6 +327,13 @@ class EC2Manager(BaseClient):
|
|||
'route': '/internal/ami-usage',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"apiReference": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'apiReference',
|
||||
'route': '/internal/api-reference',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"dbpoolStats": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
|
@ -333,7 +350,7 @@ class EC2Manager(BaseClient):
|
|||
},
|
||||
"ensureKeyPair": {
|
||||
'args': ['name'],
|
||||
'input': 'v1/create-key-pair.json#',
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#',
|
||||
'method': 'get',
|
||||
'name': 'ensureKeyPair',
|
||||
'route': '/key-pairs/<name>',
|
||||
|
@ -343,7 +360,7 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getHealth',
|
||||
'output': 'v1/health.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'route': '/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -351,7 +368,7 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getPrices',
|
||||
'output': 'v1/prices.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -359,16 +376,16 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getRecentErrors',
|
||||
'output': 'v1/errors.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'route': '/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"getSpecificPrices": {
|
||||
'args': [],
|
||||
'input': 'v1/prices-request.json#',
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#',
|
||||
'method': 'post',
|
||||
'name': 'getSpecificPrices',
|
||||
'output': 'v1/prices.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -376,7 +393,7 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listWorkerTypes',
|
||||
'output': 'v1/list-worker-types.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#',
|
||||
'route': '/worker-types',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -410,7 +427,7 @@ class EC2Manager(BaseClient):
|
|||
},
|
||||
"runInstance": {
|
||||
'args': ['workerType'],
|
||||
'input': 'v1/run-instance-request.json#',
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#',
|
||||
'method': 'put',
|
||||
'name': 'runInstance',
|
||||
'route': '/worker-types/<workerType>/instance',
|
||||
|
@ -441,7 +458,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeErrors',
|
||||
'output': 'v1/errors.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'route': '/worker-types/<workerType>/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -449,7 +466,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeHealth',
|
||||
'output': 'v1/health.json#',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'route': '/worker-types/<workerType>/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -457,7 +474,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'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',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -465,7 +482,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'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',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -13,8 +13,9 @@ _defaultConfig = config
|
|||
|
||||
class Github(BaseClient):
|
||||
"""
|
||||
The github service is responsible for creating tasks in reposnse
|
||||
to GitHub events, and posting results to the GitHub UI.
|
||||
The github service, typically available at
|
||||
`github.taskcluster.net`, is responsible for publishing pulse
|
||||
messages in response to GitHub events.
|
||||
|
||||
This document describes the API end-point for consuming GitHub
|
||||
web hooks, as well as some useful consumer APIs.
|
||||
|
@ -24,9 +25,8 @@ class Github(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://github.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,10 +22,8 @@ class GithubEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/"
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def pullRequest(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -150,43 +148,6 @@ class GithubEvents(BaseClient):
|
|||
}
|
||||
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 = {
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://hooks.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'hooks'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -108,9 +108,8 @@ class Index(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://index.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'index'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -18,21 +18,8 @@ class Login(BaseClient):
|
|||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -50,7 +37,7 @@ class Login(BaseClient):
|
|||
```
|
||||
|
||||
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
|
||||
appropriate to the user. Note that the resulting credentials may or may
|
||||
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
|
||||
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``
|
||||
"""
|
||||
|
||||
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 = {
|
||||
"oidcCredentials": {
|
||||
'args': ['provider'],
|
||||
'method': 'get',
|
||||
'name': 'oidcCredentials',
|
||||
'output': 'v1/oidc-credentials-response.json#',
|
||||
'output': 'http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json',
|
||||
'route': '/oidc-credentials/<provider>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -19,9 +19,8 @@ class Notify(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://notify.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'notify'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,21 +22,21 @@ class Pulse(BaseClient):
|
|||
"""
|
||||
|
||||
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.
|
||||
This endpoint is used to check that the service is up.
|
||||
Get an overview of the Rabbit cluster.
|
||||
|
||||
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):
|
||||
"""
|
||||
|
@ -46,9 +46,9 @@ class Pulse(BaseClient):
|
|||
|
||||
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.
|
||||
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``
|
||||
"""
|
||||
|
@ -62,7 +62,7 @@ class Pulse(BaseClient):
|
|||
Get public information about a single namespace. This is the same information
|
||||
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``
|
||||
"""
|
||||
|
@ -73,35 +73,43 @@ class Pulse(BaseClient):
|
|||
"""
|
||||
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`.
|
||||
Claim a namespace, returning a username and password with access to that
|
||||
namespace good for a short time. Clients should call this endpoint again
|
||||
at the re-claim time given in the response, as the password will be rotated
|
||||
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`,
|
||||
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 `expires` and `contact` properties can be updated at any time in a reclaim
|
||||
operation.
|
||||
|
||||
The specified `expires` time updates any existing expiration times. Connections
|
||||
for expired namespaces will be terminated.
|
||||
This method takes input: ``http://schemas.taskcluster.net/pulse/v1/namespace-request.json``
|
||||
|
||||
This method takes input: ``v1/namespace-request.json#``
|
||||
|
||||
This method gives output: ``v1/namespace-response.json#``
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace-response.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
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 = {
|
||||
"claimNamespace": {
|
||||
'args': ['namespace'],
|
||||
'input': 'v1/namespace-request.json#',
|
||||
'input': 'http://schemas.taskcluster.net/pulse/v1/namespace-request.json',
|
||||
'method': 'post',
|
||||
'name': 'claimNamespace',
|
||||
'output': 'v1/namespace-response.json#',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace-response.json',
|
||||
'route': '/namespace/<namespace>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -109,8 +117,8 @@ class Pulse(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listNamespaces',
|
||||
'output': 'v1/list-namespaces-response.json#',
|
||||
'query': ['limit', 'continuationToken'],
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json',
|
||||
'query': ['limit', 'continuation'],
|
||||
'route': '/namespaces',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -118,10 +126,18 @@ class Pulse(BaseClient):
|
|||
'args': ['namespace'],
|
||||
'method': 'get',
|
||||
'name': 'namespace',
|
||||
'output': 'v1/namespace.json#',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace.json',
|
||||
'route': '/namespace/<namespace>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"overview": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'overview',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json',
|
||||
'route': '/overview',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"ping": {
|
||||
'args': [],
|
||||
'method': 'get',
|
||||
|
|
|
@ -13,7 +13,8 @@ _defaultConfig = config
|
|||
|
||||
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.
|
||||
|
||||
This document describes the API end-point for publishing the pulse
|
||||
|
@ -21,9 +22,8 @@ class PurgeCache(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://purge-cache.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'purge-cache'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,10 +22,8 @@ class PurgeCacheEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-purge-cache/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-purge-cache/v1/"
|
||||
}
|
||||
serviceName = 'purge-cache'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def purgeCache(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -25,9 +25,8 @@ class Queue(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://queue.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -63,10 +63,8 @@ class QueueEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/"
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def taskDefined(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,9 +23,8 @@ class Secrets(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://secrets.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'secrets'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,10 +23,8 @@ class TreeherderEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/",
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/"
|
||||
}
|
||||
serviceName = 'treeherder'
|
||||
apiVersion = 'v1'
|
||||
|
||||
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
|
||||
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)
|
||||
|
||||
|
@ -61,7 +59,7 @@ class TreeherderEvents(BaseClient):
|
|||
'name': 'reserved',
|
||||
},
|
||||
],
|
||||
'schema': 'v1/pulse-job.json#',
|
||||
'schema': 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#',
|
||||
}
|
||||
return self._makeTopicExchange(ref, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import re
|
||||
import json
|
||||
|
@ -11,6 +10,7 @@ import requests.exceptions
|
|||
import slugid
|
||||
import time
|
||||
import six
|
||||
import sys
|
||||
import random
|
||||
|
||||
from . import exceptions
|
||||
|
@ -298,7 +298,7 @@ def putFile(filename, url, contentType):
|
|||
with open(filename, 'rb') as f:
|
||||
contentLength = os.fstat(f.fileno()).st_size
|
||||
return makeHttpRequest('put', url, f, headers={
|
||||
'Content-Length': str(contentLength),
|
||||
'Content-Length': contentLength,
|
||||
'Content-Type': contentType,
|
||||
})
|
||||
|
||||
|
@ -319,30 +319,89 @@ def isExpired(certificate):
|
|||
return expiry < int(time.time() * 1000) + 20 * 60
|
||||
|
||||
|
||||
def optionsFromEnvironment(defaults=None):
|
||||
"""Fetch root URL and credentials from the standard TASKCLUSTER_…
|
||||
environment variables and return them in a format suitable for passing to a
|
||||
client constructor."""
|
||||
options = defaults or {}
|
||||
credentials = options.get('credentials', {})
|
||||
def authenticate(description=None):
|
||||
"""
|
||||
Open a web-browser to login.taskcluster.net and listen on localhost for
|
||||
a callback with credentials in query-string.
|
||||
|
||||
rootUrl = os.environ.get('TASKCLUSTER_ROOT_URL')
|
||||
if rootUrl:
|
||||
options['rootUrl'] = rootUrl
|
||||
The description will be shown on login.taskcluster.net, if not provided
|
||||
a default message with script path will be displayed.
|
||||
"""
|
||||
# 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 clientId:
|
||||
credentials['clientId'] = clientId
|
||||
if not description:
|
||||
script = '[interpreter/unknown]'
|
||||
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')
|
||||
if accessToken:
|
||||
credentials['accessToken'] = accessToken
|
||||
creds = [None]
|
||||
|
||||
certificate = os.environ.get('TASKCLUSTER_CERTIFICATE')
|
||||
if certificate:
|
||||
credentials['certificate'] = certificate
|
||||
class AuthCallBackRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def log_message(format, *args):
|
||||
pass
|
||||
|
||||
if credentials:
|
||||
options['credentials'] = credentials
|
||||
def do_GET(self):
|
||||
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 with subjectAsync.createSession(loop=loop) as session:
|
||||
client = subjectAsync.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -50,14 +49,13 @@ class TestAuthenticationAsync(base.TCTest):
|
|||
['test:xyz'],
|
||||
)
|
||||
client = subjectAsync.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
}, session=session)
|
||||
|
||||
result = await client.testAuthenticate({
|
||||
result = client.testAuthenticate({
|
||||
'clientScopes': ['test:*'],
|
||||
'requiredScopes': ['test:xyz'],
|
||||
})
|
||||
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 re
|
||||
import json
|
||||
import copy
|
||||
|
||||
import mock
|
||||
import httmock
|
||||
|
@ -17,7 +16,6 @@ import base
|
|||
import taskcluster.auth as subject
|
||||
import taskcluster.exceptions as exc
|
||||
import taskcluster.utils as utils
|
||||
import taskcluster_urls as liburls
|
||||
|
||||
|
||||
class ClientTest(base.TCTest):
|
||||
|
@ -47,7 +45,7 @@ class ClientTest(base.TCTest):
|
|||
]
|
||||
self.apiRef = base.createApiRef(entries=entries)
|
||||
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
|
||||
sleepPatcher = mock.patch('time.sleep')
|
||||
sleepSleep = sleepPatcher.start()
|
||||
|
@ -58,32 +56,6 @@ class ClientTest(base.TCTest):
|
|||
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):
|
||||
|
||||
def test_valid_no_subs(self):
|
||||
|
@ -312,8 +284,6 @@ class ObjWithDotJson(object):
|
|||
|
||||
class TestMakeHttpRequest(ClientTest):
|
||||
|
||||
apiPath = liburls.api(ClientTest.test_root_url, 'fake', 'v1', 'test')
|
||||
|
||||
def setUp(self):
|
||||
|
||||
ClientTest.setUp(self)
|
||||
|
@ -323,8 +293,8 @@ class TestMakeHttpRequest(ClientTest):
|
|||
expected = {'test': 'works'}
|
||||
p.return_value = ObjWithDotJson(200, expected)
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'test', None)
|
||||
p.assert_called_once_with('GET', self.apiPath, None, mock.ANY)
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
p.assert_called_once_with('GET', 'http://www.example.com', None, mock.ANY)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
def test_success_first_try_payload(self):
|
||||
|
@ -332,8 +302,8 @@ class TestMakeHttpRequest(ClientTest):
|
|||
expected = {'test': 'works'}
|
||||
p.return_value = ObjWithDotJson(200, expected)
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'test', {'payload': 2})
|
||||
p.assert_called_once_with('GET', self.apiPath,
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', {'payload': 2})
|
||||
p.assert_called_once_with('GET', 'http://www.example.com',
|
||||
utils.dumpJson({'payload': 2}), mock.ANY)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
|
@ -348,10 +318,10 @@ class TestMakeHttpRequest(ClientTest):
|
|||
ObjWithDotJson(200, expected)
|
||||
]
|
||||
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'])]
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'test', None)
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
p.assert_has_calls(expectedCalls)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
|
@ -373,12 +343,12 @@ class TestMakeHttpRequest(ClientTest):
|
|||
ObjWithDotJson(200, {'got this': 'wrong'})
|
||||
]
|
||||
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)]
|
||||
|
||||
with self.assertRaises(exc.TaskclusterRestFailure):
|
||||
try:
|
||||
self.client._makeHttpRequest('GET', 'test', None)
|
||||
self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
except exc.TaskclusterRestFailure as err:
|
||||
self.assertEqual('msg', str(err))
|
||||
self.assertEqual(500, err.status_code)
|
||||
|
@ -397,34 +367,43 @@ class TestMakeHttpRequest(ClientTest):
|
|||
ObjWithDotJson(200, expected)
|
||||
]
|
||||
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'])]
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'test', None)
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
p.assert_has_calls(expectedCalls)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
def test_failure_status_code(self):
|
||||
with mock.patch.object(utils, 'makeSingleHttpRequest') as p:
|
||||
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'])]
|
||||
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)
|
||||
|
||||
def test_failure_connection_errors(self):
|
||||
with mock.patch.object(utils, 'makeSingleHttpRequest') as p:
|
||||
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'])]
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
prevMaxRetries = subject._defaultConfig['maxRetries']
|
||||
with mock.patch.dict(subject._defaultConfig, {'maxRetries': prevMaxRetries + 1}):
|
||||
|
@ -436,10 +415,7 @@ class TestOptions(ClientTest):
|
|||
'clientId': u"\U0001F4A9",
|
||||
}
|
||||
with self.assertRaises(exc.TaskclusterAuthFailure):
|
||||
subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': badCredentials,
|
||||
})
|
||||
subject.Auth({'credentials': badCredentials})
|
||||
|
||||
|
||||
class TestMakeApiCall(ClientTest):
|
||||
|
@ -542,13 +518,13 @@ class TestTopicExchange(ClientTest):
|
|||
self.assertEqual(expected, actual['routingKeyPattern'])
|
||||
|
||||
def test_exchange(self):
|
||||
expected = 'exchange/taskcluster-fake/v1/topicExchange'
|
||||
expected = 'test/v1/topicExchange'
|
||||
actual = self.client.topicName('')
|
||||
self.assertEqual(expected, actual['exchange'])
|
||||
|
||||
def test_exchange_trailing_slash(self):
|
||||
self.client.options['exchangePrefix'] = 'exchange/taskcluster-fake2/v1/'
|
||||
expected = 'exchange/taskcluster-fake2/v1/topicExchange'
|
||||
self.client.options['exchangePrefix'] = 'test/v1/'
|
||||
expected = 'test/v1/topicExchange'
|
||||
actual = self.client.topicName('')
|
||||
self.assertEqual(expected, actual['exchange'])
|
||||
|
||||
|
@ -580,17 +556,18 @@ class TestTopicExchange(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):
|
||||
expected = 'https://fake.taskcluster.net/v1/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):
|
||||
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/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):
|
||||
expected = 'https://fake.taskcluster.net/v1/two_args_no_input/arg0/arg1?qs0=1'
|
||||
actual = self.client.buildUrl(
|
||||
'two_args_no_input',
|
||||
params={
|
||||
|
@ -599,7 +576,7 @@ class TestBuildUrl(ClientTest):
|
|||
},
|
||||
query={'qs0': 1}
|
||||
)
|
||||
self.assertEqual(self.apiPath + '?qs0=1', actual)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_fails_to_build_url_for_missing_method(self):
|
||||
with self.assertRaises(exc.TaskclusterFailure):
|
||||
|
@ -612,17 +589,17 @@ class TestBuildUrl(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):
|
||||
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 = re.sub('bewit=[^&]*', 'bewit=X', actual)
|
||||
self.assertEqual(self.apiPath + '?bewit=X', actual)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
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 = re.sub('bewit=[^&]*', 'bewit=X', actual)
|
||||
self.assertEqual(self.apiPath + '?bewit=X', actual)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
class TestMockHttpCalls(ClientTest):
|
||||
|
@ -645,30 +622,30 @@ class TestMockHttpCalls(ClientTest):
|
|||
def test_no_args_no_input(self):
|
||||
with httmock.HTTMock(self.fakeSite):
|
||||
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):
|
||||
with httmock.HTTMock(self.fakeSite):
|
||||
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):
|
||||
with httmock.HTTMock(self.fakeSite):
|
||||
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})
|
||||
|
||||
def test_no_args_with_empty_input(self):
|
||||
with httmock.HTTMock(self.fakeSite):
|
||||
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), {})
|
||||
|
||||
def test_two_args_with_input(self):
|
||||
with httmock.HTTMock(self.fakeSite):
|
||||
self.client.two_args_with_input('a', 'b', {'x': 1})
|
||||
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})
|
||||
|
||||
def test_kwargs(self):
|
||||
|
@ -676,7 +653,7 @@ class TestMockHttpCalls(ClientTest):
|
|||
self.client.two_args_with_input(
|
||||
{'x': 1}, arg0='a', arg1='b')
|
||||
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})
|
||||
|
||||
|
||||
|
@ -690,14 +667,14 @@ class TestAuthentication(base.TCTest):
|
|||
@httmock.all_requests
|
||||
def auth_response(url, request):
|
||||
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)
|
||||
headers = {'content-type': 'application/json'}
|
||||
content = {"clientId": "abc"}
|
||||
return httmock.response(200, content, headers, None, 5, request)
|
||||
|
||||
with httmock.HTTMock(auth_response):
|
||||
client = subject.Auth({"rootUrl": "https://tc-tests.example.com", "credentials": {}})
|
||||
client = subject.Auth({"credentials": {}})
|
||||
result = client.client('abc')
|
||||
self.assertEqual(result, {"clientId": "abc"})
|
||||
|
||||
|
@ -705,7 +682,6 @@ class TestAuthentication(base.TCTest):
|
|||
"""we can call methods which require authentication with valid
|
||||
permacreds"""
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -719,7 +695,6 @@ class TestAuthentication(base.TCTest):
|
|||
|
||||
def test_permacred_simple_authorizedScopes(self):
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -736,7 +711,6 @@ class TestAuthentication(base.TCTest):
|
|||
def test_unicode_permacred_simple(self):
|
||||
"""Unicode strings that encode to ASCII in credentials do not cause issues"""
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': u'tester',
|
||||
'accessToken': u'no-secret',
|
||||
|
@ -752,7 +726,6 @@ class TestAuthentication(base.TCTest):
|
|||
"""Unicode strings that do not encode to ASCII in credentials cause issues"""
|
||||
with self.assertRaises(exc.TaskclusterAuthFailure):
|
||||
subject.Auth({
|
||||
'rootUrl': self.test_root_url,
|
||||
'credentials': {
|
||||
'clientId': u"\U0001F4A9",
|
||||
'accessToken': u"\U0001F4A9",
|
||||
|
@ -762,7 +735,6 @@ class TestAuthentication(base.TCTest):
|
|||
def test_permacred_insufficient_scopes(self):
|
||||
"""A call with insufficient scopes results in an error"""
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -787,7 +759,6 @@ class TestAuthentication(base.TCTest):
|
|||
['test:xyz'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
})
|
||||
|
||||
|
@ -807,7 +778,6 @@ class TestAuthentication(base.TCTest):
|
|||
name='credName'
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
})
|
||||
|
||||
|
@ -826,7 +796,6 @@ class TestAuthentication(base.TCTest):
|
|||
['test:xyz:*'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
'authorizedScopes': ['test:xyz:abc'],
|
||||
})
|
||||
|
@ -848,7 +817,6 @@ class TestAuthentication(base.TCTest):
|
|||
name='credName'
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
'authorizedScopes': ['test:xyz:abc'],
|
||||
})
|
||||
|
@ -863,7 +831,6 @@ class TestAuthentication(base.TCTest):
|
|||
def test_signed_url(self):
|
||||
"""we can use a signed url built with the python client"""
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -881,7 +848,6 @@ class TestAuthentication(base.TCTest):
|
|||
|
||||
def test_signed_url_bad_credentials(self):
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'wrong-secret',
|
||||
|
@ -902,7 +868,6 @@ class TestAuthentication(base.TCTest):
|
|||
['test:*'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
})
|
||||
signedUrl = client.buildSignedUrl('testAuthenticateGet')
|
||||
|
@ -916,7 +881,6 @@ class TestAuthentication(base.TCTest):
|
|||
|
||||
def test_signed_url_authorizedScopes(self):
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -941,7 +905,6 @@ class TestAuthentication(base.TCTest):
|
|||
['test:*'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
'authorizedScopes': ['test:authenticate-get'],
|
||||
})
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import datetime
|
||||
import uuid
|
||||
import os
|
||||
|
||||
import taskcluster.utils as subject
|
||||
import dateutil.parser
|
||||
|
@ -339,101 +338,3 @@ class TestIsExpired(TestCase):
|
|||
}
|
||||
""")
|
||||
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}',
|
||||
},
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче