зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1508381 - vendor newest taskcluster client r=tomprince
Differential Revision: https://phabricator.services.mozilla.com/D18025 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
1fded4473e
Коммит
14e7864a6a
|
@ -12,7 +12,7 @@ python-hglib==2.4
|
|||
redo==2.0.3
|
||||
requests==2.9.1
|
||||
six==1.10.0
|
||||
taskcluster==4.0.1
|
||||
taskcluster==6.0.0
|
||||
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==4.0.1 \
|
||||
--hash=sha256:27256511044346ac71a495d3c636f2add95c102b9b09f90d6fb1ea3e9949d311 \
|
||||
--hash=sha256:99dd90bc1c566968868c8b07ede32f8e031cbccd52c7195a61e802679d461447 \
|
||||
--hash=sha256:d0360063c1a3fcaaa514bb31c03954ba573d2b671df40a2ecfdfd9339cc8e93e
|
||||
taskcluster==6.0.0 \
|
||||
--hash=sha256:48ecd4898c7928deddfb34cb1cfe2b2505c68416e6c503f8a7f3dd0572425e96 \
|
||||
--hash=sha256:6d5cf7bdbc09dc48b2d376b418b95c1c157a2d359c4b6b231c1fb14a323c0cc5 \
|
||||
--hash=sha256:e409fce7a72808e4f87dc7baca7a79d8b64d5c5045264b9e197c120cc40e219b
|
||||
virtualenv-clone==0.3.0 \
|
||||
--hash=sha256:4507071d81013fd03ea9930ec26bc8648b997927a11fa80e8ee81198b57e0ac7 \
|
||||
--hash=sha256:b5cfe535d14dc68dfc1d1bb4ac1209ea28235b91156e2bba8e250d291c3fb4f8 \
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: taskcluster
|
||||
Version: 4.0.1
|
||||
Version: 6.0.0
|
||||
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.5`. The async objects use asyncio coroutines for concurrency; this
|
||||
`python>=3.6`. 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,7 +101,10 @@ Here's a slide deck for an [introduction to async python](https://gitpitch.com/e
|
|||
|
||||
```python
|
||||
import taskcluster
|
||||
index = taskcluster.Index({'credentials': {'clientId': 'id', 'accessToken': 'accessToken'}})
|
||||
index = taskcluster.Index({
|
||||
'rootUrl': 'https://tc.example.com',
|
||||
'credentials': {'clientId': 'id', 'accessToken': 'accessToken'},
|
||||
})
|
||||
index.ping()
|
||||
```
|
||||
|
||||
|
@ -119,18 +122,34 @@ Here's a slide deck for an [introduction to async python](https://gitpitch.com/e
|
|||
|
||||
```python
|
||||
from taskcluster import client
|
||||
qEvt = client.QueueEvents()
|
||||
qEvt = client.QueueEvents({rootUrl: 'https://tc.example.com'})
|
||||
# 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()
|
||||
queue = taskcluster.Queue({'rootUrl': 'https://tc.example.com'})
|
||||
i = 0
|
||||
tasks = 0
|
||||
outcome = queue.listTaskGroup('JzTGxwxhQ76_Tt1dxkaG5g')
|
||||
|
@ -153,7 +172,7 @@ built and then counted:
|
|||
|
||||
```python
|
||||
import taskcluster
|
||||
queue = taskcluster.Queue()
|
||||
queue = taskcluster.Queue({'rootUrl': 'https://tc.example.com'})
|
||||
|
||||
responses = []
|
||||
|
||||
|
@ -1011,9 +1030,9 @@ await asyncAuth.testAuthenticateGet() # -> result
|
|||
import taskcluster
|
||||
authEvents = taskcluster.AuthEvents(options)
|
||||
```
|
||||
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.
|
||||
The auth service 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
|
||||
|
@ -1488,11 +1507,23 @@ 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](http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#)
|
||||
Required [output schema](v1/list-worker-types.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1510,7 +1541,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#)
|
||||
Required [input schema](v1/run-instance-request.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1548,7 +1579,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#)
|
||||
Required [output schema](v1/worker-type-resources.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1568,7 +1599,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/health.json#)
|
||||
Required [output schema](v1/health.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1588,7 +1619,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/errors.json#)
|
||||
Required [output schema](v1/errors.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1608,7 +1639,7 @@ Takes the following arguments:
|
|||
|
||||
* `workerType`
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#)
|
||||
Required [output schema](v1/worker-type-state.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1628,7 +1659,7 @@ Takes the following arguments:
|
|||
|
||||
* `name`
|
||||
|
||||
Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#)
|
||||
Required [input schema](v1/create-key-pair.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1680,7 +1711,7 @@ await asyncEC2Manager.terminateInstance(region='value', instanceId='value') # ->
|
|||
Return a list of possible prices for EC2
|
||||
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/prices.json#)
|
||||
Required [output schema](v1/prices.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1693,9 +1724,9 @@ await asyncEC2Manager.getPrices() # -> result
|
|||
Return a list of possible prices for EC2
|
||||
|
||||
|
||||
Required [input schema](http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#)
|
||||
Required [input schema](v1/prices-request.json#)
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/prices.json#)
|
||||
Required [output schema](v1/prices.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1708,7 +1739,7 @@ await asyncEC2Manager.getSpecificPrices(payload) # -> result
|
|||
Give some basic stats on the health of our EC2 account
|
||||
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/health.json#)
|
||||
Required [output schema](v1/health.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1721,7 +1752,7 @@ await asyncEC2Manager.getHealth() # -> result
|
|||
Return a list of recent errors encountered
|
||||
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/ec2-manager/v1/errors.json#)
|
||||
Required [output schema](v1/errors.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -1821,29 +1852,6 @@ 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
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1860,9 +1868,8 @@ loop = asyncio.get_event_loop()
|
|||
session = taskcluster.aio.createSession(loop=loop)
|
||||
asyncGithub = taskcluster.aio.Github(options, session=session)
|
||||
```
|
||||
The github service, typically available at
|
||||
`github.taskcluster.net`, is responsible for publishing pulse
|
||||
messages in response to GitHub events.
|
||||
The github service is responsible for creating tasks in reposnse
|
||||
to GitHub events, and posting results to the GitHub UI.
|
||||
|
||||
This document describes the API end-point for consuming GitHub
|
||||
web hooks, as well as some useful consumer APIs.
|
||||
|
@ -2056,6 +2063,12 @@ 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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2089,7 +2102,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
|
||||
https://docs.taskcluster.net/reference/core/taskcluster-hooks/docs/firing-hooks
|
||||
[/docs/reference/core/taskcluster-hooks/docs/firing-hooks](firing-hooks)
|
||||
for more information.
|
||||
#### Ping Server
|
||||
Respond without doing anything.
|
||||
|
@ -2623,6 +2636,18 @@ 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
|
||||
|
@ -2636,7 +2661,7 @@ Authorization: Bearer abc.xyz
|
|||
```
|
||||
|
||||
The `access_token` is first verified against the named
|
||||
:provider, then passed to the provider's API to retrieve a user
|
||||
:provider, then passed to the provider's APIBuilder 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
|
||||
|
@ -2652,7 +2677,7 @@ Takes the following arguments:
|
|||
|
||||
* `provider`
|
||||
|
||||
Required [output schema](http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json)
|
||||
Required [output schema](v1/oidc-credentials-response.json#)
|
||||
|
||||
```python
|
||||
# Sync calls
|
||||
|
@ -2663,18 +2688,6 @@ 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
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -2761,6 +2774,111 @@ 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
|
||||
|
@ -2774,8 +2892,7 @@ loop = asyncio.get_event_loop()
|
|||
session = taskcluster.aio.createSession(loop=loop)
|
||||
asyncPurgeCache = taskcluster.aio.PurgeCache(options, session=session)
|
||||
```
|
||||
The purge-cache service, typically available at
|
||||
`purge-cache.taskcluster.net`, is responsible for publishing a pulse
|
||||
The purge-cache service 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 = '4.0.1'
|
||||
VERSION = '6.0.0'
|
||||
|
||||
tests_require = [
|
||||
'nose==1.3.7',
|
||||
|
@ -30,6 +30,7 @@ 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,6 +9,7 @@ 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,6 +9,7 @@ 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._joinBaseUrlAndRoute(route)
|
||||
url = self._constructUrl(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:
|
||||
except (ValueError, aiohttp.client_exceptions.ContentTypeError):
|
||||
return {"response": response}
|
||||
|
||||
# This code-path should be unreachable
|
||||
|
@ -239,6 +239,8 @@ class AsyncBaseClient(BaseClient):
|
|||
|
||||
|
||||
def createApiClient(name, api):
|
||||
api = api['reference']
|
||||
|
||||
attributes = dict(
|
||||
name=name,
|
||||
__doc__=api.get('description'),
|
||||
|
@ -246,12 +248,22 @@ def createApiClient(name, api):
|
|||
funcinfo={},
|
||||
)
|
||||
|
||||
copiedOptions = ('baseUrl', 'exchangePrefix')
|
||||
for opt in copiedOptions:
|
||||
if opt in api['reference']:
|
||||
attributes['classOptions'][opt] = api['reference'][opt]
|
||||
# apply a default for apiVersion; this can be removed when all services
|
||||
# have apiVersion
|
||||
if 'apiVersion' not in api:
|
||||
api['apiVersion'] = 'v1'
|
||||
|
||||
for entry in api['reference']['entries']:
|
||||
copiedOptions = ('exchangePrefix',)
|
||||
for opt in copiedOptions:
|
||||
if opt in api:
|
||||
attributes['classOptions'][opt] = api[opt]
|
||||
|
||||
copiedProperties = ('serviceName', 'apiVersion')
|
||||
for opt in copiedProperties:
|
||||
if opt in api:
|
||||
attributes[opt] = api[opt]
|
||||
|
||||
for entry in api['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': contentLength,
|
||||
'Content-Length': str(contentLength),
|
||||
'Content-Type': contentType,
|
||||
}, session=session)
|
||||
|
|
|
@ -55,8 +55,9 @@ 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, typically available at `auth.taskcluster.net`
|
||||
is responsible for storing credentials, managing assignment of scopes,
|
||||
and validation of request signatures from other services.
|
||||
The auth service 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,8 +24,10 @@ class AuthEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/",
|
||||
}
|
||||
serviceName = 'auth'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def clientCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -44,8 +44,9 @@ class AwsProvisioner(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://aws-provisioner.taskcluster.net/v1"
|
||||
}
|
||||
serviceName = 'aws-provisioner'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def listWorkerTypeSummaries(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -17,8 +17,9 @@ class AwsProvisionerEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/",
|
||||
}
|
||||
apiVersion = 'v1'
|
||||
|
||||
def workerTypeCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -17,8 +17,21 @@ 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):
|
||||
"""
|
||||
|
@ -26,7 +39,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
This method is only for debugging the ec2-manager
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#``
|
||||
This method gives output: ``v1/list-worker-types.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -39,7 +52,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Request an instance of a worker type
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#``
|
||||
This method takes input: ``v1/run-instance-request.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -63,7 +76,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return an object which has a generic state description. This only contains counts of instances
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#``
|
||||
This method gives output: ``v1/worker-type-resources.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -76,7 +89,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return a view of the health of a given worker type
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
|
||||
This method gives output: ``v1/health.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -89,7 +102,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return a list of the most recent errors encountered by a worker type
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
|
||||
This method gives output: ``v1/errors.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -102,7 +115,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return state information for a given worker type
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#``
|
||||
This method gives output: ``v1/worker-type-state.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -115,7 +128,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Idempotently ensure that a keypair of a given name exists
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#``
|
||||
This method takes input: ``v1/create-key-pair.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -150,7 +163,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return a list of possible prices for EC2
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
|
||||
This method gives output: ``v1/prices.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -163,9 +176,9 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return a list of possible prices for EC2
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#``
|
||||
This method takes input: ``v1/prices-request.json#``
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
|
||||
This method gives output: ``v1/prices.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -178,7 +191,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Give some basic stats on the health of our EC2 account
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
|
||||
This method gives output: ``v1/health.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -191,7 +204,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
|
||||
Return a list of recent errors encountered
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
|
||||
This method gives output: ``v1/errors.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -289,29 +302,6 @@ 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': [],
|
||||
|
@ -327,13 +317,6 @@ 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',
|
||||
|
@ -350,7 +333,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
},
|
||||
"ensureKeyPair": {
|
||||
'args': ['name'],
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#',
|
||||
'input': 'v1/create-key-pair.json#',
|
||||
'method': 'get',
|
||||
'name': 'ensureKeyPair',
|
||||
'route': '/key-pairs/<name>',
|
||||
|
@ -360,7 +343,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getHealth',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'output': 'v1/health.json#',
|
||||
'route': '/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -368,7 +351,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getPrices',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'output': 'v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -376,16 +359,16 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getRecentErrors',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'output': 'v1/errors.json#',
|
||||
'route': '/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"getSpecificPrices": {
|
||||
'args': [],
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#',
|
||||
'input': 'v1/prices-request.json#',
|
||||
'method': 'post',
|
||||
'name': 'getSpecificPrices',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'output': 'v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -393,7 +376,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listWorkerTypes',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#',
|
||||
'output': 'v1/list-worker-types.json#',
|
||||
'route': '/worker-types',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -427,7 +410,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
},
|
||||
"runInstance": {
|
||||
'args': ['workerType'],
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#',
|
||||
'input': 'v1/run-instance-request.json#',
|
||||
'method': 'put',
|
||||
'name': 'runInstance',
|
||||
'route': '/worker-types/<workerType>/instance',
|
||||
|
@ -458,7 +441,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeErrors',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'output': 'v1/errors.json#',
|
||||
'route': '/worker-types/<workerType>/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -466,7 +449,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeHealth',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'output': 'v1/health.json#',
|
||||
'route': '/worker-types/<workerType>/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -474,7 +457,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeState',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#',
|
||||
'output': 'v1/worker-type-state.json#',
|
||||
'route': '/worker-types/<workerType>/state',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -482,7 +465,7 @@ class EC2Manager(AsyncBaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeStats',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#',
|
||||
'output': 'v1/worker-type-resources.json#',
|
||||
'route': '/worker-types/<workerType>/stats',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -13,9 +13,8 @@ _defaultConfig = config
|
|||
|
||||
class Github(AsyncBaseClient):
|
||||
"""
|
||||
The github service, typically available at
|
||||
`github.taskcluster.net`, is responsible for publishing pulse
|
||||
messages in response to GitHub events.
|
||||
The github service is responsible for creating tasks in reposnse
|
||||
to GitHub events, and posting results to the GitHub UI.
|
||||
|
||||
This document describes the API end-point for consuming GitHub
|
||||
web hooks, as well as some useful consumer APIs.
|
||||
|
@ -25,8 +24,9 @@ class Github(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://github.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,8 +22,10 @@ class GithubEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/",
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def pullRequest(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -148,6 +150,43 @@ 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,13 +30,14 @@ 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
|
||||
https://docs.taskcluster.net/reference/core/taskcluster-hooks/docs/firing-hooks
|
||||
[/docs/reference/core/taskcluster-hooks/docs/firing-hooks](firing-hooks)
|
||||
for more information.
|
||||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://hooks.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'hooks'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -108,8 +108,9 @@ class Index(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://index.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'index'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -18,8 +18,21 @@ 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):
|
||||
"""
|
||||
|
@ -37,7 +50,7 @@ class Login(AsyncBaseClient):
|
|||
```
|
||||
|
||||
The `access_token` is first verified against the named
|
||||
:provider, then passed to the provider's API to retrieve a user
|
||||
:provider, then passed to the provider's APIBuilder 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
|
||||
|
@ -47,31 +60,19 @@ 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: ``http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json``
|
||||
This method gives output: ``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': 'http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json',
|
||||
'output': 'v1/oidc-credentials-response.json#',
|
||||
'route': '/oidc-credentials/<provider>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -19,8 +19,9 @@ class Notify(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://notify.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'notify'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,74 +22,9 @@ class Pulse(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://pulse.taskcluster.net/v1"
|
||||
}
|
||||
|
||||
async def overview(self, *args, **kwargs):
|
||||
"""
|
||||
Rabbit Overview
|
||||
|
||||
Get an overview of the Rabbit cluster.
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return await self._makeApiCall(self.funcinfo["overview"], *args, **kwargs)
|
||||
|
||||
async def listNamespaces(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return await self._makeApiCall(self.funcinfo["listNamespaces"], *args, **kwargs)
|
||||
|
||||
async def namespace(self, *args, **kwargs):
|
||||
"""
|
||||
Get a namespace
|
||||
|
||||
Get public information about a single namespace. This is the same information
|
||||
as returned by `listNamespaces`.
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return await self._makeApiCall(self.funcinfo["namespace"], *args, **kwargs)
|
||||
|
||||
async def claimNamespace(self, *args, **kwargs):
|
||||
"""
|
||||
Claim a namespace
|
||||
|
||||
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.
|
||||
|
||||
The `expires` and `contact` properties can be updated at any time in a reclaim
|
||||
operation.
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/pulse/v1/namespace-request.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)
|
||||
serviceName = 'pulse'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -103,13 +38,70 @@ class Pulse(AsyncBaseClient):
|
|||
|
||||
return await self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
|
||||
|
||||
async def listNamespaces(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
|
||||
This method gives output: ``v1/list-namespaces-response.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return await self._makeApiCall(self.funcinfo["listNamespaces"], *args, **kwargs)
|
||||
|
||||
async def namespace(self, *args, **kwargs):
|
||||
"""
|
||||
Get a namespace
|
||||
|
||||
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 is ``experimental``
|
||||
"""
|
||||
|
||||
return await self._makeApiCall(self.funcinfo["namespace"], *args, **kwargs)
|
||||
|
||||
async def claimNamespace(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
|
||||
This method takes input: ``v1/namespace-request.json#``
|
||||
|
||||
This method gives output: ``v1/namespace-response.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return await self._makeApiCall(self.funcinfo["claimNamespace"], *args, **kwargs)
|
||||
|
||||
funcinfo = {
|
||||
"claimNamespace": {
|
||||
'args': ['namespace'],
|
||||
'input': 'http://schemas.taskcluster.net/pulse/v1/namespace-request.json',
|
||||
'input': 'v1/namespace-request.json#',
|
||||
'method': 'post',
|
||||
'name': 'claimNamespace',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace-response.json',
|
||||
'output': 'v1/namespace-response.json#',
|
||||
'route': '/namespace/<namespace>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -117,8 +109,8 @@ class Pulse(AsyncBaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listNamespaces',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json',
|
||||
'query': ['limit', 'continuation'],
|
||||
'output': 'v1/list-namespaces-response.json#',
|
||||
'query': ['limit', 'continuationToken'],
|
||||
'route': '/namespaces',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -126,18 +118,10 @@ class Pulse(AsyncBaseClient):
|
|||
'args': ['namespace'],
|
||||
'method': 'get',
|
||||
'name': 'namespace',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace.json',
|
||||
'output': '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,8 +13,7 @@ _defaultConfig = config
|
|||
|
||||
class PurgeCache(AsyncBaseClient):
|
||||
"""
|
||||
The purge-cache service, typically available at
|
||||
`purge-cache.taskcluster.net`, is responsible for publishing a pulse
|
||||
The purge-cache service 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
|
||||
|
@ -22,8 +21,9 @@ class PurgeCache(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://purge-cache.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'purge-cache'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,8 +22,10 @@ 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,8 +25,9 @@ class Queue(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://queue.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -63,8 +63,10 @@ class QueueEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/",
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def taskDefined(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,8 +23,9 @@ class Secrets(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://secrets.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'secrets'
|
||||
apiVersion = 'v1'
|
||||
|
||||
async def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,8 +23,10 @@ class TreeherderEvents(AsyncBaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/",
|
||||
}
|
||||
serviceName = 'treeherder'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def jobs(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -33,7 +35,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: ``http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#``This exchange takes the following keys:
|
||||
This exchange outputs: ``v1/pulse-job.json#``This exchange takes the following keys:
|
||||
|
||||
* destination: destination (required)
|
||||
|
||||
|
@ -59,7 +61,7 @@ class TreeherderEvents(AsyncBaseClient):
|
|||
'name': 'reserved',
|
||||
},
|
||||
],
|
||||
'schema': 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#',
|
||||
'schema': 'v1/pulse-job.json#',
|
||||
}
|
||||
return self._makeTopicExchange(ref, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -55,8 +55,9 @@ 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, typically available at `auth.taskcluster.net`
|
||||
is responsible for storing credentials, managing assignment of scopes,
|
||||
and validation of request signatures from other services.
|
||||
The auth service 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,8 +24,10 @@ class AuthEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-auth/v1/",
|
||||
}
|
||||
serviceName = 'auth'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def clientCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -44,8 +44,9 @@ class AwsProvisioner(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://aws-provisioner.taskcluster.net/v1"
|
||||
}
|
||||
serviceName = 'aws-provisioner'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def listWorkerTypeSummaries(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -17,8 +17,9 @@ class AwsProvisionerEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-aws-provisioner/v1/",
|
||||
}
|
||||
apiVersion = 'v1'
|
||||
|
||||
def workerTypeCreated(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import copy
|
||||
|
@ -21,6 +20,7 @@ import mohawk.bewit
|
|||
|
||||
import taskcluster.exceptions as exceptions
|
||||
import taskcluster.utils as utils
|
||||
import taskcluster_urls as liburls
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,10 +28,11 @@ log = logging.getLogger(__name__)
|
|||
# Default configuration
|
||||
_defaultConfig = config = {
|
||||
'credentials': {
|
||||
'clientId': os.environ.get('TASKCLUSTER_CLIENT_ID'),
|
||||
'accessToken': os.environ.get('TASKCLUSTER_ACCESS_TOKEN'),
|
||||
'certificate': os.environ.get('TASKCLUSTER_CERTIFICATE'),
|
||||
'clientId': None,
|
||||
'accessToken': None,
|
||||
'certificate': None,
|
||||
},
|
||||
'rootUrl': None,
|
||||
'maxRetries': 5,
|
||||
'signedUrlExpiration': 15 * 60,
|
||||
}
|
||||
|
@ -52,10 +53,14 @@ 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:
|
||||
|
@ -67,6 +72,7 @@ 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')
|
||||
|
@ -168,7 +174,7 @@ class BaseClient(object):
|
|||
route = self._subArgsInRoute(entry, routeParams)
|
||||
if query:
|
||||
route += '?' + urllib.parse.urlencode(query)
|
||||
return self._joinBaseUrlAndRoute(route)
|
||||
return liburls.api(self.options['rootUrl'], self.serviceName, self.apiVersion, route)
|
||||
|
||||
def buildSignedUrl(self, methodName, *args, **kwargs):
|
||||
""" Build a signed URL. This URL contains the credentials needed to access
|
||||
|
@ -237,11 +243,14 @@ class BaseClient(object):
|
|||
u.fragment,
|
||||
))
|
||||
|
||||
def _joinBaseUrlAndRoute(self, route):
|
||||
return urllib.parse.urljoin(
|
||||
'{}/'.format(self.options['baseUrl'].rstrip('/')),
|
||||
route.lstrip('/')
|
||||
)
|
||||
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 _makeApiCall(self, entry, *args, **kwargs):
|
||||
""" This function is used to dispatch calls to other functions
|
||||
|
@ -437,7 +446,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._joinBaseUrlAndRoute(route)
|
||||
url = self._constructUrl(route)
|
||||
log.debug('Full URL used is: %s', url)
|
||||
|
||||
hawkExt = self.makeHawkExt()
|
||||
|
@ -545,6 +554,8 @@ class BaseClient(object):
|
|||
|
||||
|
||||
def createApiClient(name, api):
|
||||
api = api['reference']
|
||||
|
||||
attributes = dict(
|
||||
name=name,
|
||||
__doc__=api.get('description'),
|
||||
|
@ -552,12 +563,22 @@ def createApiClient(name, api):
|
|||
funcinfo={},
|
||||
)
|
||||
|
||||
copiedOptions = ('baseUrl', 'exchangePrefix')
|
||||
for opt in copiedOptions:
|
||||
if opt in api['reference']:
|
||||
attributes['classOptions'][opt] = api['reference'][opt]
|
||||
# apply a default for apiVersion; this can be removed when all services
|
||||
# have apiVersion
|
||||
if 'apiVersion' not in api:
|
||||
api['apiVersion'] = 'v1'
|
||||
|
||||
for entry in api['reference']['entries']:
|
||||
copiedOptions = ('exchangePrefix',)
|
||||
for opt in copiedOptions:
|
||||
if opt in api:
|
||||
attributes['classOptions'][opt] = api[opt]
|
||||
|
||||
copiedProperties = ('serviceName', 'apiVersion')
|
||||
for opt in copiedProperties:
|
||||
if opt in api:
|
||||
attributes[opt] = api[opt]
|
||||
|
||||
for entry in api['entries']:
|
||||
if entry['type'] == 'function':
|
||||
def addApiCall(e):
|
||||
def apiCall(self, *args, **kwargs):
|
||||
|
|
|
@ -17,8 +17,21 @@ 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):
|
||||
"""
|
||||
|
@ -26,7 +39,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
This method is only for debugging the ec2-manager
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#``
|
||||
This method gives output: ``v1/list-worker-types.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -39,7 +52,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Request an instance of a worker type
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#``
|
||||
This method takes input: ``v1/run-instance-request.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -63,7 +76,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return an object which has a generic state description. This only contains counts of instances
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#``
|
||||
This method gives output: ``v1/worker-type-resources.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -76,7 +89,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return a view of the health of a given worker type
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
|
||||
This method gives output: ``v1/health.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -89,7 +102,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return a list of the most recent errors encountered by a worker type
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
|
||||
This method gives output: ``v1/errors.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -102,7 +115,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return state information for a given worker type
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#``
|
||||
This method gives output: ``v1/worker-type-state.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -115,7 +128,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Idempotently ensure that a keypair of a given name exists
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#``
|
||||
This method takes input: ``v1/create-key-pair.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -150,7 +163,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return a list of possible prices for EC2
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
|
||||
This method gives output: ``v1/prices.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -163,9 +176,9 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return a list of possible prices for EC2
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#``
|
||||
This method takes input: ``v1/prices-request.json#``
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/prices.json#``
|
||||
This method gives output: ``v1/prices.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -178,7 +191,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Give some basic stats on the health of our EC2 account
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/health.json#``
|
||||
This method gives output: ``v1/health.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -191,7 +204,7 @@ class EC2Manager(BaseClient):
|
|||
|
||||
Return a list of recent errors encountered
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/ec2-manager/v1/errors.json#``
|
||||
This method gives output: ``v1/errors.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
@ -289,29 +302,6 @@ 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': [],
|
||||
|
@ -327,13 +317,6 @@ 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',
|
||||
|
@ -350,7 +333,7 @@ class EC2Manager(BaseClient):
|
|||
},
|
||||
"ensureKeyPair": {
|
||||
'args': ['name'],
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/create-key-pair.json#',
|
||||
'input': 'v1/create-key-pair.json#',
|
||||
'method': 'get',
|
||||
'name': 'ensureKeyPair',
|
||||
'route': '/key-pairs/<name>',
|
||||
|
@ -360,7 +343,7 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getHealth',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'output': 'v1/health.json#',
|
||||
'route': '/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -368,7 +351,7 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getPrices',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'output': 'v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -376,16 +359,16 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'getRecentErrors',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'output': 'v1/errors.json#',
|
||||
'route': '/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
"getSpecificPrices": {
|
||||
'args': [],
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/prices-request.json#',
|
||||
'input': 'v1/prices-request.json#',
|
||||
'method': 'post',
|
||||
'name': 'getSpecificPrices',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/prices.json#',
|
||||
'output': 'v1/prices.json#',
|
||||
'route': '/prices',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -393,7 +376,7 @@ class EC2Manager(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listWorkerTypes',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/list-worker-types.json#',
|
||||
'output': 'v1/list-worker-types.json#',
|
||||
'route': '/worker-types',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -427,7 +410,7 @@ class EC2Manager(BaseClient):
|
|||
},
|
||||
"runInstance": {
|
||||
'args': ['workerType'],
|
||||
'input': 'http://schemas.taskcluster.net/ec2-manager/v1/run-instance-request.json#',
|
||||
'input': 'v1/run-instance-request.json#',
|
||||
'method': 'put',
|
||||
'name': 'runInstance',
|
||||
'route': '/worker-types/<workerType>/instance',
|
||||
|
@ -458,7 +441,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeErrors',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/errors.json#',
|
||||
'output': 'v1/errors.json#',
|
||||
'route': '/worker-types/<workerType>/errors',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -466,7 +449,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeHealth',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/health.json#',
|
||||
'output': 'v1/health.json#',
|
||||
'route': '/worker-types/<workerType>/health',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -474,7 +457,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeState',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-state.json#',
|
||||
'output': 'v1/worker-type-state.json#',
|
||||
'route': '/worker-types/<workerType>/state',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -482,7 +465,7 @@ class EC2Manager(BaseClient):
|
|||
'args': ['workerType'],
|
||||
'method': 'get',
|
||||
'name': 'workerTypeStats',
|
||||
'output': 'http://schemas.taskcluster.net/ec2-manager/v1/worker-type-resources.json#',
|
||||
'output': 'v1/worker-type-resources.json#',
|
||||
'route': '/worker-types/<workerType>/stats',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -13,9 +13,8 @@ _defaultConfig = config
|
|||
|
||||
class Github(BaseClient):
|
||||
"""
|
||||
The github service, typically available at
|
||||
`github.taskcluster.net`, is responsible for publishing pulse
|
||||
messages in response to GitHub events.
|
||||
The github service is responsible for creating tasks in reposnse
|
||||
to GitHub events, and posting results to the GitHub UI.
|
||||
|
||||
This document describes the API end-point for consuming GitHub
|
||||
web hooks, as well as some useful consumer APIs.
|
||||
|
@ -25,8 +24,9 @@ class Github(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://github.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,8 +22,10 @@ class GithubEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-github/v1/",
|
||||
}
|
||||
serviceName = 'github'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def pullRequest(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -148,6 +150,43 @@ 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,13 +30,14 @@ 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
|
||||
https://docs.taskcluster.net/reference/core/taskcluster-hooks/docs/firing-hooks
|
||||
[/docs/reference/core/taskcluster-hooks/docs/firing-hooks](firing-hooks)
|
||||
for more information.
|
||||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://hooks.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'hooks'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -108,8 +108,9 @@ class Index(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://index.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'index'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -18,8 +18,21 @@ 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):
|
||||
"""
|
||||
|
@ -37,7 +50,7 @@ class Login(BaseClient):
|
|||
```
|
||||
|
||||
The `access_token` is first verified against the named
|
||||
:provider, then passed to the provider's API to retrieve a user
|
||||
:provider, then passed to the provider's APIBuilder 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
|
||||
|
@ -47,31 +60,19 @@ 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: ``http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json``
|
||||
This method gives output: ``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': 'http://schemas.taskcluster.net/login/v1/oidc-credentials-response.json',
|
||||
'output': 'v1/oidc-credentials-response.json#',
|
||||
'route': '/oidc-credentials/<provider>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
|
|
@ -19,8 +19,9 @@ class Notify(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://notify.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'notify'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,74 +22,9 @@ class Pulse(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://pulse.taskcluster.net/v1"
|
||||
}
|
||||
|
||||
def overview(self, *args, **kwargs):
|
||||
"""
|
||||
Rabbit Overview
|
||||
|
||||
Get an overview of the Rabbit cluster.
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/rabbit-overview.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return self._makeApiCall(self.funcinfo["overview"], *args, **kwargs)
|
||||
|
||||
def listNamespaces(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return self._makeApiCall(self.funcinfo["listNamespaces"], *args, **kwargs)
|
||||
|
||||
def namespace(self, *args, **kwargs):
|
||||
"""
|
||||
Get a namespace
|
||||
|
||||
Get public information about a single namespace. This is the same information
|
||||
as returned by `listNamespaces`.
|
||||
|
||||
This method gives output: ``http://schemas.taskcluster.net/pulse/v1/namespace.json``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return self._makeApiCall(self.funcinfo["namespace"], *args, **kwargs)
|
||||
|
||||
def claimNamespace(self, *args, **kwargs):
|
||||
"""
|
||||
Claim a namespace
|
||||
|
||||
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.
|
||||
|
||||
The `expires` and `contact` properties can be updated at any time in a reclaim
|
||||
operation.
|
||||
|
||||
This method takes input: ``http://schemas.taskcluster.net/pulse/v1/namespace-request.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)
|
||||
serviceName = 'pulse'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -103,13 +38,70 @@ class Pulse(BaseClient):
|
|||
|
||||
return self._makeApiCall(self.funcinfo["ping"], *args, **kwargs)
|
||||
|
||||
def listNamespaces(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
|
||||
This method gives output: ``v1/list-namespaces-response.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return self._makeApiCall(self.funcinfo["listNamespaces"], *args, **kwargs)
|
||||
|
||||
def namespace(self, *args, **kwargs):
|
||||
"""
|
||||
Get a namespace
|
||||
|
||||
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 is ``experimental``
|
||||
"""
|
||||
|
||||
return self._makeApiCall(self.funcinfo["namespace"], *args, **kwargs)
|
||||
|
||||
def claimNamespace(self, *args, **kwargs):
|
||||
"""
|
||||
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.
|
||||
|
||||
This method takes input: ``v1/namespace-request.json#``
|
||||
|
||||
This method gives output: ``v1/namespace-response.json#``
|
||||
|
||||
This method is ``experimental``
|
||||
"""
|
||||
|
||||
return self._makeApiCall(self.funcinfo["claimNamespace"], *args, **kwargs)
|
||||
|
||||
funcinfo = {
|
||||
"claimNamespace": {
|
||||
'args': ['namespace'],
|
||||
'input': 'http://schemas.taskcluster.net/pulse/v1/namespace-request.json',
|
||||
'input': 'v1/namespace-request.json#',
|
||||
'method': 'post',
|
||||
'name': 'claimNamespace',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace-response.json',
|
||||
'output': 'v1/namespace-response.json#',
|
||||
'route': '/namespace/<namespace>',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -117,8 +109,8 @@ class Pulse(BaseClient):
|
|||
'args': [],
|
||||
'method': 'get',
|
||||
'name': 'listNamespaces',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/list-namespaces-response.json',
|
||||
'query': ['limit', 'continuation'],
|
||||
'output': 'v1/list-namespaces-response.json#',
|
||||
'query': ['limit', 'continuationToken'],
|
||||
'route': '/namespaces',
|
||||
'stability': 'experimental',
|
||||
},
|
||||
|
@ -126,18 +118,10 @@ class Pulse(BaseClient):
|
|||
'args': ['namespace'],
|
||||
'method': 'get',
|
||||
'name': 'namespace',
|
||||
'output': 'http://schemas.taskcluster.net/pulse/v1/namespace.json',
|
||||
'output': '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,8 +13,7 @@ _defaultConfig = config
|
|||
|
||||
class PurgeCache(BaseClient):
|
||||
"""
|
||||
The purge-cache service, typically available at
|
||||
`purge-cache.taskcluster.net`, is responsible for publishing a pulse
|
||||
The purge-cache service 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
|
||||
|
@ -22,8 +21,9 @@ class PurgeCache(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://purge-cache.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'purge-cache'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -22,8 +22,10 @@ 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,8 +25,9 @@ class Queue(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://queue.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -63,8 +63,10 @@ class QueueEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-queue/v1/",
|
||||
}
|
||||
serviceName = 'queue'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def taskDefined(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,8 +23,9 @@ class Secrets(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"baseUrl": "https://secrets.taskcluster.net/v1/"
|
||||
}
|
||||
serviceName = 'secrets'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def ping(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
@ -23,8 +23,10 @@ class TreeherderEvents(BaseClient):
|
|||
"""
|
||||
|
||||
classOptions = {
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/"
|
||||
"exchangePrefix": "exchange/taskcluster-treeherder/v1/",
|
||||
}
|
||||
serviceName = 'treeherder'
|
||||
apiVersion = 'v1'
|
||||
|
||||
def jobs(self, *args, **kwargs):
|
||||
"""
|
||||
|
@ -33,7 +35,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: ``http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#``This exchange takes the following keys:
|
||||
This exchange outputs: ``v1/pulse-job.json#``This exchange takes the following keys:
|
||||
|
||||
* destination: destination (required)
|
||||
|
||||
|
@ -59,7 +61,7 @@ class TreeherderEvents(BaseClient):
|
|||
'name': 'reserved',
|
||||
},
|
||||
],
|
||||
'schema': 'http://schemas.taskcluster.net/taskcluster-treeherder/v1/pulse-job.json#',
|
||||
'schema': 'v1/pulse-job.json#',
|
||||
}
|
||||
return self._makeTopicExchange(ref, *args, **kwargs)
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: UTF-8 -*-
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import re
|
||||
import json
|
||||
|
@ -10,7 +11,6 @@ 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': contentLength,
|
||||
'Content-Length': str(contentLength),
|
||||
'Content-Type': contentType,
|
||||
})
|
||||
|
||||
|
@ -319,89 +319,30 @@ def isExpired(certificate):
|
|||
return expiry < int(time.time() * 1000) + 20 * 60
|
||||
|
||||
|
||||
def authenticate(description=None):
|
||||
"""
|
||||
Open a web-browser to login.taskcluster.net and listen on localhost for
|
||||
a callback with credentials in query-string.
|
||||
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', {})
|
||||
|
||||
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
|
||||
rootUrl = os.environ.get('TASKCLUSTER_ROOT_URL')
|
||||
if rootUrl:
|
||||
options['rootUrl'] = rootUrl
|
||||
|
||||
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
|
||||
)
|
||||
clientId = os.environ.get('TASKCLUSTER_CLIENT_ID')
|
||||
if clientId:
|
||||
credentials['clientId'] = clientId
|
||||
|
||||
creds = [None]
|
||||
accessToken = os.environ.get('TASKCLUSTER_ACCESS_TOKEN')
|
||||
if accessToken:
|
||||
credentials['accessToken'] = accessToken
|
||||
|
||||
class AuthCallBackRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
def log_message(format, *args):
|
||||
pass
|
||||
certificate = os.environ.get('TASKCLUSTER_CERTIFICATE')
|
||||
if certificate:
|
||||
credentials['certificate'] = certificate
|
||||
|
||||
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
|
||||
if credentials:
|
||||
options['credentials'] = credentials
|
||||
|
||||
# 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]
|
||||
return options
|
||||
|
|
|
@ -21,6 +21,7 @@ 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',
|
||||
|
@ -49,13 +50,14 @@ class TestAuthenticationAsync(base.TCTest):
|
|||
['test:xyz'],
|
||||
)
|
||||
client = subjectAsync.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
}, session=session)
|
||||
|
||||
result = client.testAuthenticate({
|
||||
result = await client.testAuthenticate({
|
||||
'clientScopes': ['test:*'],
|
||||
'requiredScopes': ['test:xyz'],
|
||||
})
|
||||
self.assertEqual(result, {'scopes': ['test:xyz'], 'clientId': 'tester'})
|
||||
|
||||
loop.run_until_complete
|
||||
loop.run_until_complete(x())
|
||||
|
|
|
@ -7,6 +7,7 @@ from six.moves import urllib
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
import copy
|
||||
|
||||
import mock
|
||||
import httmock
|
||||
|
@ -16,6 +17,7 @@ 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):
|
||||
|
@ -45,7 +47,7 @@ class ClientTest(base.TCTest):
|
|||
]
|
||||
self.apiRef = base.createApiRef(entries=entries)
|
||||
self.clientClass = subject.createApiClient('testApi', self.apiRef)
|
||||
self.client = self.clientClass()
|
||||
self.client = self.clientClass({'rootUrl': self.test_root_url})
|
||||
# Patch time.sleep so that we don't delay tests
|
||||
sleepPatcher = mock.patch('time.sleep')
|
||||
sleepSleep = sleepPatcher.start()
|
||||
|
@ -56,6 +58,32 @@ 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):
|
||||
|
@ -168,10 +196,10 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_1_with_payload(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test', 'input': True},
|
||||
1,
|
||||
2,
|
||||
{'A': 123}
|
||||
{'args': ['k1', 'k2'], 'name': 'test', 'input': True},
|
||||
1,
|
||||
2,
|
||||
{'A': 123}
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, {'A': 123})
|
||||
|
@ -185,8 +213,8 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_2_with_payload(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test', 'input': True},
|
||||
{'A': 123}, k1=1, k2=2
|
||||
{'args': ['k1', 'k2'], 'name': 'test', 'input': True},
|
||||
{'A': 123}, k1=1, k2=2
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, {'A': 123})
|
||||
|
@ -194,8 +222,8 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_3_without_payload_without_query(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2}
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2}
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, None)
|
||||
|
@ -203,9 +231,9 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_3_with_payload_without_query(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2},
|
||||
payload={'A': 123}
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2},
|
||||
payload={'A': 123}
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, {'A': 123})
|
||||
|
@ -213,10 +241,10 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_3_with_payload_with_query(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2},
|
||||
payload={'A': 123},
|
||||
query={'B': 456}
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2},
|
||||
payload={'A': 123},
|
||||
query={'B': 456}
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, {'A': 123})
|
||||
|
@ -224,9 +252,9 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_3_without_payload_with_query(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2},
|
||||
query={'B': 456}
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
params={'k1': 1, 'k2': 2},
|
||||
query={'B': 456}
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, None)
|
||||
|
@ -234,11 +262,11 @@ class TestProcessArgs(ClientTest):
|
|||
|
||||
def test_calling_convention_3_with_positional_arguments_with_payload_with_query(self):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
1,
|
||||
2,
|
||||
query={'B': 456},
|
||||
payload={'A': 123}
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
1,
|
||||
2,
|
||||
query={'B': 456},
|
||||
payload={'A': 123}
|
||||
)
|
||||
self.assertEqual(params, {'k1': 1, 'k2': 2})
|
||||
self.assertEqual(payload, {'A': 123})
|
||||
|
@ -258,12 +286,12 @@ class TestProcessArgs(ClientTest):
|
|||
def test_calling_convention_3_with_pos_args_same_as_param_kwarg_dict_vals_with_payload_with_query(self):
|
||||
with self.assertRaises(exc.TaskclusterFailure):
|
||||
params, payload, query, _, _ = self.client._processArgs(
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
1,
|
||||
2,
|
||||
params={'k1': 1, 'k2': 2},
|
||||
query={'B': 456},
|
||||
payload={'A': 123}
|
||||
{'args': ['k1', 'k2'], 'name': 'test'},
|
||||
1,
|
||||
2,
|
||||
params={'k1': 1, 'k2': 2},
|
||||
query={'B': 456},
|
||||
payload={'A': 123}
|
||||
)
|
||||
|
||||
|
||||
|
@ -284,6 +312,8 @@ class ObjWithDotJson(object):
|
|||
|
||||
class TestMakeHttpRequest(ClientTest):
|
||||
|
||||
apiPath = liburls.api(ClientTest.test_root_url, 'fake', 'v1', 'test')
|
||||
|
||||
def setUp(self):
|
||||
|
||||
ClientTest.setUp(self)
|
||||
|
@ -293,8 +323,8 @@ class TestMakeHttpRequest(ClientTest):
|
|||
expected = {'test': 'works'}
|
||||
p.return_value = ObjWithDotJson(200, expected)
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
p.assert_called_once_with('GET', 'http://www.example.com', None, mock.ANY)
|
||||
v = self.client._makeHttpRequest('GET', 'test', None)
|
||||
p.assert_called_once_with('GET', self.apiPath, None, mock.ANY)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
def test_success_first_try_payload(self):
|
||||
|
@ -302,8 +332,8 @@ class TestMakeHttpRequest(ClientTest):
|
|||
expected = {'test': 'works'}
|
||||
p.return_value = ObjWithDotJson(200, expected)
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', {'payload': 2})
|
||||
p.assert_called_once_with('GET', 'http://www.example.com',
|
||||
v = self.client._makeHttpRequest('GET', 'test', {'payload': 2})
|
||||
p.assert_called_once_with('GET', self.apiPath,
|
||||
utils.dumpJson({'payload': 2}), mock.ANY)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
|
@ -318,10 +348,10 @@ class TestMakeHttpRequest(ClientTest):
|
|||
ObjWithDotJson(200, expected)
|
||||
]
|
||||
p.side_effect = sideEffect
|
||||
expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
|
||||
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY)
|
||||
for x in range(self.client.options['maxRetries'])]
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
v = self.client._makeHttpRequest('GET', 'test', None)
|
||||
p.assert_has_calls(expectedCalls)
|
||||
self.assertEqual(expected, v)
|
||||
|
||||
|
@ -343,12 +373,12 @@ class TestMakeHttpRequest(ClientTest):
|
|||
ObjWithDotJson(200, {'got this': 'wrong'})
|
||||
]
|
||||
p.side_effect = sideEffect
|
||||
expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
|
||||
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY)
|
||||
for x in range(self.client.options['maxRetries'] + 1)]
|
||||
|
||||
with self.assertRaises(exc.TaskclusterRestFailure):
|
||||
try:
|
||||
self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
self.client._makeHttpRequest('GET', 'test', None)
|
||||
except exc.TaskclusterRestFailure as err:
|
||||
self.assertEqual('msg', str(err))
|
||||
self.assertEqual(500, err.status_code)
|
||||
|
@ -367,43 +397,34 @@ class TestMakeHttpRequest(ClientTest):
|
|||
ObjWithDotJson(200, expected)
|
||||
]
|
||||
p.side_effect = sideEffect
|
||||
expectedCalls = [mock.call('GET', 'http://www.example.com', None, mock.ANY)
|
||||
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY)
|
||||
for x in range(self.client.options['maxRetries'])]
|
||||
|
||||
v = self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
v = self.client._makeHttpRequest('GET', 'test', 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', 'http://www.example.com', None, mock.ANY)
|
||||
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY)
|
||||
for x in range(self.client.options['maxRetries'])]
|
||||
with self.assertRaises(exc.TaskclusterRestFailure):
|
||||
self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
self.client._makeHttpRequest('GET', 'test', 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', 'http://www.example.com', None, mock.ANY)
|
||||
expectedCalls = [mock.call('GET', self.apiPath, None, mock.ANY)
|
||||
for x in range(self.client.options['maxRetries'])]
|
||||
with self.assertRaises(exc.TaskclusterConnectionError):
|
||||
self.client._makeHttpRequest('GET', 'http://www.example.com', None)
|
||||
self.client._makeHttpRequest('GET', 'test', 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}):
|
||||
|
@ -415,7 +436,10 @@ class TestOptions(ClientTest):
|
|||
'clientId': u"\U0001F4A9",
|
||||
}
|
||||
with self.assertRaises(exc.TaskclusterAuthFailure):
|
||||
subject.Auth({'credentials': badCredentials})
|
||||
subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': badCredentials,
|
||||
})
|
||||
|
||||
|
||||
class TestMakeApiCall(ClientTest):
|
||||
|
@ -518,13 +542,13 @@ class TestTopicExchange(ClientTest):
|
|||
self.assertEqual(expected, actual['routingKeyPattern'])
|
||||
|
||||
def test_exchange(self):
|
||||
expected = 'test/v1/topicExchange'
|
||||
expected = 'exchange/taskcluster-fake/v1/topicExchange'
|
||||
actual = self.client.topicName('')
|
||||
self.assertEqual(expected, actual['exchange'])
|
||||
|
||||
def test_exchange_trailing_slash(self):
|
||||
self.client.options['exchangePrefix'] = 'test/v1/'
|
||||
expected = 'test/v1/topicExchange'
|
||||
self.client.options['exchangePrefix'] = 'exchange/taskcluster-fake2/v1/'
|
||||
expected = 'exchange/taskcluster-fake2/v1/topicExchange'
|
||||
actual = self.client.topicName('')
|
||||
self.assertEqual(expected, actual['exchange'])
|
||||
|
||||
|
@ -556,18 +580,17 @@ 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(expected, actual)
|
||||
self.assertEqual(self.apiPath, 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(expected, actual)
|
||||
self.assertEqual(self.apiPath, 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={
|
||||
|
@ -576,7 +599,7 @@ class TestBuildUrl(ClientTest):
|
|||
},
|
||||
query={'qs0': 1}
|
||||
)
|
||||
self.assertEqual(expected, actual)
|
||||
self.assertEqual(self.apiPath + '?qs0=1', actual)
|
||||
|
||||
def test_fails_to_build_url_for_missing_method(self):
|
||||
with self.assertRaises(exc.TaskclusterFailure):
|
||||
|
@ -589,17 +612,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(expected, actual)
|
||||
self.assertEqual(self.apiPath + '?bewit=X', 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(expected, actual)
|
||||
self.assertEqual(self.apiPath + '?bewit=X', actual)
|
||||
|
||||
|
||||
class TestMockHttpCalls(ClientTest):
|
||||
|
@ -622,30 +645,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://fake.taskcluster.net/v1/no_args_no_input')
|
||||
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/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://fake.taskcluster.net/v1/two_args_no_input/1/2')
|
||||
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/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://fake.taskcluster.net/v1/no_args_with_input')
|
||||
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/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://fake.taskcluster.net/v1/no_args_with_input')
|
||||
self.assertEqual(self.gotUrl, 'https://tc-tests.example.com/api/fake/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://fake.taskcluster.net/v1/two_args_with_input/a/b')
|
||||
'https://tc-tests.example.com/api/fake/v1/two_args_with_input/a/b')
|
||||
self.assertEqual(json.loads(self.gotRequest.body), {"x": 1})
|
||||
|
||||
def test_kwargs(self):
|
||||
|
@ -653,7 +676,7 @@ class TestMockHttpCalls(ClientTest):
|
|||
self.client.two_args_with_input(
|
||||
{'x': 1}, arg0='a', arg1='b')
|
||||
self.assertEqual(self.gotUrl,
|
||||
'https://fake.taskcluster.net/v1/two_args_with_input/a/b')
|
||||
'https://tc-tests.example.com/api/fake/v1/two_args_with_input/a/b')
|
||||
self.assertEqual(json.loads(self.gotRequest.body), {"x": 1})
|
||||
|
||||
|
||||
|
@ -667,14 +690,14 @@ class TestAuthentication(base.TCTest):
|
|||
@httmock.all_requests
|
||||
def auth_response(url, request):
|
||||
self.assertEqual(urllib.parse.urlunsplit(url),
|
||||
'https://auth.taskcluster.net/v1/clients/abc')
|
||||
'https://tc-tests.example.com/api/auth/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({"credentials": {}})
|
||||
client = subject.Auth({"rootUrl": "https://tc-tests.example.com", "credentials": {}})
|
||||
result = client.client('abc')
|
||||
self.assertEqual(result, {"clientId": "abc"})
|
||||
|
||||
|
@ -682,6 +705,7 @@ 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',
|
||||
|
@ -695,6 +719,7 @@ class TestAuthentication(base.TCTest):
|
|||
|
||||
def test_permacred_simple_authorizedScopes(self):
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -711,6 +736,7 @@ 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',
|
||||
|
@ -726,6 +752,7 @@ 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",
|
||||
|
@ -735,6 +762,7 @@ 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',
|
||||
|
@ -759,6 +787,7 @@ class TestAuthentication(base.TCTest):
|
|||
['test:xyz'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
})
|
||||
|
||||
|
@ -778,6 +807,7 @@ class TestAuthentication(base.TCTest):
|
|||
name='credName'
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
})
|
||||
|
||||
|
@ -796,6 +826,7 @@ class TestAuthentication(base.TCTest):
|
|||
['test:xyz:*'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
'authorizedScopes': ['test:xyz:abc'],
|
||||
})
|
||||
|
@ -817,6 +848,7 @@ class TestAuthentication(base.TCTest):
|
|||
name='credName'
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
'authorizedScopes': ['test:xyz:abc'],
|
||||
})
|
||||
|
@ -831,6 +863,7 @@ 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',
|
||||
|
@ -848,6 +881,7 @@ 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',
|
||||
|
@ -868,6 +902,7 @@ class TestAuthentication(base.TCTest):
|
|||
['test:*'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
})
|
||||
signedUrl = client.buildSignedUrl('testAuthenticateGet')
|
||||
|
@ -881,6 +916,7 @@ class TestAuthentication(base.TCTest):
|
|||
|
||||
def test_signed_url_authorizedScopes(self):
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': {
|
||||
'clientId': 'tester',
|
||||
'accessToken': 'no-secret',
|
||||
|
@ -905,6 +941,7 @@ class TestAuthentication(base.TCTest):
|
|||
['test:*'],
|
||||
)
|
||||
client = subject.Auth({
|
||||
'rootUrl': self.real_root_url,
|
||||
'credentials': tempCred,
|
||||
'authorizedScopes': ['test:authenticate-get'],
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
import uuid
|
||||
import os
|
||||
|
||||
import taskcluster.utils as subject
|
||||
import dateutil.parser
|
||||
|
@ -338,3 +339,101 @@ 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}',
|
||||
},
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче