Skip to content
37 changes: 37 additions & 0 deletions examples/sandbox_runtime_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import print_function

import os

from sandbox_common import cleanup_sandbox, create_sandbox, run_example


def main():
sandbox = create_sandbox(timeout=300)
try:
listed = sandbox.client.list_sandboxes_v2(
template=[sandbox.template_id],
)
print('sandboxes using template:', listed)

print('current injections:', sandbox.get_injections())
sandbox.update_injections([{
'type': 'http',
'base_url': 'https://api.example.com/v1/*',
'headers': {'X-From-Sandbox': 'qiniu-python-sdk'},
}])
print('updated injections:', sandbox.get_injections())

github_token = os.getenv('QINIU_SANDBOX_GITHUB_TOKEN')
if github_token:
sandbox.update_github_token(github_token)
print('updated GitHub token')
else:
print('GitHub token update skipped: '
'QINIU_SANDBOX_GITHUB_TOKEN is not set')
finally:
cleanup_sandbox(sandbox)


if __name__ == '__main__':
run_example(main)
3 changes: 3 additions & 0 deletions examples/sandbox_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def main():
created.get('id')
)
print('template:', created)
details = client.get_template(template_id)
print('names:', details.get('names'))
print('owned by requesting team:', details.get('isOwner'))
finally:
if template_id:
try:
Expand Down
54 changes: 52 additions & 2 deletions qiniu/services/sandbox/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,15 @@ def _normalize_list_options(opts):
opts['metadata'] = metadata
if isinstance(opts.get('metadata'), dict):
opts['metadata'] = urlencode(opts.get('metadata'))
if query.get('state') is not None:
opts['state'] = query.get('state')
for key in ('state', 'template'):
if query.get(key) is not None:
opts[key] = query.get(key)
Comment thread
miclle marked this conversation as resolved.
Outdated
Comment thread
miclle marked this conversation as resolved.
Outdated
value = opts.get(key)
if value is not None and not isinstance(value, basestring):
Comment thread
miclle marked this conversation as resolved.
Outdated
try:
opts[key] = ','.join(str(item) for item in value)
Comment thread
miclle marked this conversation as resolved.
Outdated
except TypeError:
opts[key] = str(value)
return opts


Expand Down Expand Up @@ -375,6 +382,49 @@ def update_sandbox(self, sandbox_id, **opts):

updateSandbox = update_sandbox

def get_sandbox_injections(self, sandbox_id):
_require_sandbox_id(sandbox_id)
return self._request(
'GET',
'/sandboxes/{0}/injections'.format(encode_path(sandbox_id)),
)

getSandboxInjections = get_sandbox_injections

def update_sandbox_injections(self, sandbox_id, injections):
_require_sandbox_id(sandbox_id)
if injections is None:
raise SandboxError('injections is required')
return self._request(
Comment thread
miclle marked this conversation as resolved.
'PUT',
'/sandboxes/{0}/injections'.format(encode_path(sandbox_id)),
body={
'injections': [
_normalize_injection(item) for item in injections
],
},
empty=True,
)

updateSandboxInjections = update_sandbox_injections

def update_sandbox_github_token(
self, sandbox_id, authorization_token=None, **opts):
_require_sandbox_id(sandbox_id)
if authorization_token is None:
authorization_token = (
opts.get('authorizationToken') or opts.get('token'))
if not authorization_token:
raise SandboxError('authorization_token is required')
return self._request(
'PUT',
'/sandboxes/{0}/github-token'.format(encode_path(sandbox_id)),
body={'authorization_token': authorization_token},
empty=True,
)

updateSandboxGithubToken = update_sandbox_github_token

def get_sandbox_metrics(self, sandbox_id, **opts):
_require_sandbox_id(sandbox_id)
return self._request(
Expand Down
17 changes: 17 additions & 0 deletions qiniu/services/sandbox/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,23 @@ def update_network(self, network):

updateNetwork = update_network

def get_injections(self):
return self.client.get_sandbox_injections(self.sandbox_id)

getInjections = get_injections

def update_injections(self, injections):
return self.client.update_sandbox_injections(
self.sandbox_id, injections)

updateInjections = update_injections

def update_github_token(self, authorization_token=None, **opts):
return self.client.update_sandbox_github_token(
self.sandbox_id, authorization_token, **opts)

updateGithubToken = update_github_token

def get_info(self):
return self.client.get_sandbox(self.sandbox_id)

Expand Down
240 changes: 240 additions & 0 deletions tests/cases/test_services/test_sandbox/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,246 @@ def test_list_sandboxes_v2_accepts_metadata_string():
assert query['metadata'] == ['user=abc&app=prod']


def test_list_sandboxes_v2_serializes_template_filters_as_comma_string():
session = RecordingSession([DummyResponse(200, {'items': []})])
client = SandboxClient(api_key='api-key', session=session)

client.list_sandboxes_v2(template=['python', 'team/node'])

query = parse_qs(urlparse(session.requests[0].url).query)
assert query['template'] == ['python,team/node']


def test_list_sandboxes_v2_serializes_array_filters_from_query_options():
session = RecordingSession([DummyResponse(200, {'items': []})])
client = SandboxClient(api_key='api-key', session=session)

client.list_sandboxes_v2(query={
'template': ['python', 'team/node'],
'state': ['running', 'paused'],
})

query = parse_qs(urlparse(session.requests[0].url).query)
assert query['template'] == ['python,team/node']
assert query['state'] == ['running,paused']


def test_list_sandboxes_v2_serializes_non_string_filter_values():
session = RecordingSession([DummyResponse(200, {'items': []})])
client = SandboxClient(api_key='api-key', session=session)

client.list_sandboxes_v2(template=[123, 456], state=7)

query = parse_qs(urlparse(session.requests[0].url).query)
assert query['template'] == ['123,456']
assert query['state'] == ['7']


def test_template_responses_preserve_names_and_ownership_metadata():
session = RecordingSession([DummyResponse(200, {
'templateID': 'tmpl123',
'aliases': ['python'],
'names': ['qiniu/python'],
'isOwner': False,
'builds': [],
})])
client = SandboxClient(api_key='api-key', session=session)

template = client.get_template('tmpl123')

assert template['aliases'] == ['python']
assert template['names'] == ['qiniu/python']
assert template['isOwner'] is False
assert template['builds'] == []


def test_get_sandbox_injections_returns_current_rules():
session = RecordingSession([DummyResponse(200, {
'injections': [{'type': 'id', 'ruleID': 'rule123'}],
})])
client = SandboxClient(api_key='api-key', session=session)

result = client.get_sandbox_injections('sandbox/id')

assert result == {
'injections': [{'type': 'id', 'ruleID': 'rule123'}],
}
request = session.requests[0]
assert request.method == 'GET'
assert request.url == (
DEFAULT_ENDPOINT + '/sandboxes/sandbox%2Fid/injections')


def test_update_sandbox_injections_replaces_normalized_rules():
session = RecordingSession([DummyResponse(204)])
client = SandboxClient(api_key='api-key', session=session)

result = client.update_sandbox_injections('sbx123', [{
'type': 'openai',
'baseUrl': 'https://api.openai.com/v1/*',
'apiKey': 'secret',
}])

assert result is None
request = session.requests[0]
assert request.method == 'PUT'
assert request.url == DEFAULT_ENDPOINT + '/sandboxes/sbx123/injections'
assert body_of(request) == {'injections': [{
'type': 'openai',
'base_url': 'https://api.openai.com/v1/*',
'api_key': 'secret',
}]}


def test_update_sandbox_injections_accepts_empty_replacement():
session = RecordingSession([DummyResponse(204)])
client = SandboxClient(api_key='api-key', session=session)

assert client.update_sandbox_injections('sbx123', []) is None
assert body_of(session.requests[0]) == {'injections': []}


def test_update_sandbox_injections_requires_rules_value():
client = SandboxClient(api_key='api-key', session=RecordingSession())

with pytest.raises(SandboxError) as err:
client.update_sandbox_injections('sbx123', None)

assert 'injections is required' in str(err.value)
assert client.session.requests == []

Comment thread
miclle marked this conversation as resolved.

def test_update_sandbox_github_token_uses_api_field_name():
session = RecordingSession([DummyResponse(204)])
client = SandboxClient(api_key='api-key', session=session)

result = client.update_sandbox_github_token(
'sbx123', authorization_token='github-token')

assert result is None
request = session.requests[0]
assert request.method == 'PUT'
assert request.url == DEFAULT_ENDPOINT + '/sandboxes/sbx123/github-token'
assert body_of(request) == {'authorization_token': 'github-token'}


def test_update_sandbox_github_token_accepts_camel_case_alias():
session = RecordingSession([DummyResponse(204)])
client = SandboxClient(api_key='api-key', session=session)

client.update_sandbox_github_token(
'sbx123', authorizationToken='github-token')

assert body_of(session.requests[0]) == {
'authorization_token': 'github-token',
}


def test_update_sandbox_github_token_requires_token():
client = SandboxClient(api_key='api-key', session=RecordingSession())

with pytest.raises(SandboxError) as err:
client.update_sandbox_github_token('sbx123')

assert 'authorization_token is required' in str(err.value)
assert client.session.requests == []


@pytest.mark.parametrize('method,args', [
('get_sandbox_injections', ()),
('update_sandbox_injections', ([],)),
('update_sandbox_github_token', ('github-token',)),
])
def test_runtime_configuration_methods_require_sandbox_id(method, args):
client = SandboxClient(api_key='api-key', session=RecordingSession())

with pytest.raises(SandboxError) as err:
getattr(client, method)(None, *args)

assert 'sandbox_id is required' in str(err.value)
assert client.session.requests == []


def test_runtime_configuration_camel_case_aliases():
session = RecordingSession([
DummyResponse(200, {'injections': []}),
DummyResponse(204),
DummyResponse(204),
])
client = SandboxClient(api_key='api-key', session=session)

assert client.getSandboxInjections('sbx123') == {'injections': []}
assert client.updateSandboxInjections('sbx123', []) is None
assert client.updateSandboxGithubToken('sbx123', 'github-token') is None

assert [request.method for request in session.requests] == [
'GET', 'PUT', 'PUT',
]


def test_sandbox_runtime_configuration_helpers_delegate_to_client():
class RuntimeConfigClient(object):
def __init__(self):
self.calls = []

def get_sandbox_injections(self, sandbox_id):
self.calls.append(('get', sandbox_id))
return {'injections': []}

def update_sandbox_injections(self, sandbox_id, injections):
self.calls.append(('injections', sandbox_id, injections))

def update_sandbox_github_token(self, sandbox_id, token):
self.calls.append(('github', sandbox_id, token))

client = RuntimeConfigClient()
sandbox = Sandbox(client=client, sandbox_id='sbx123')

assert sandbox.get_injections() == {'injections': []}
assert sandbox.update_injections([{'type': 'id', 'ruleID': 'rule123'}]) \
is None
assert sandbox.update_github_token('github-token') is None
assert client.calls == [
('get', 'sbx123'),
('injections', 'sbx123', [{'type': 'id', 'ruleID': 'rule123'}]),
('github', 'sbx123', 'github-token'),
]


def test_sandbox_runtime_configuration_camel_case_aliases_delegate():
class RuntimeConfigClient(object):
def get_sandbox_injections(self, sandbox_id):
return {'sandboxID': sandbox_id, 'injections': []}

def update_sandbox_injections(self, sandbox_id, injections):
return sandbox_id, injections

def update_sandbox_github_token(self, sandbox_id, token):
return sandbox_id, token

sandbox = Sandbox(client=RuntimeConfigClient(), sandbox_id='sbx123')

assert sandbox.getInjections()['sandboxID'] == 'sbx123'
assert sandbox.updateInjections([]) == ('sbx123', [])
assert sandbox.updateGithubToken('token') == ('sbx123', 'token')


def test_sandbox_github_token_helper_forwards_keyword_aliases():
class RuntimeConfigClient(object):
def update_sandbox_github_token(
self, sandbox_id, authorization_token=None, **opts):
return sandbox_id, authorization_token, opts

sandbox = Sandbox(client=RuntimeConfigClient(), sandbox_id='sbx123')

assert sandbox.update_github_token(token='token') == (
'sbx123', None, {'token': 'token'},
)
assert sandbox.updateGithubToken(authorizationToken='camel-token') == (
'sbx123', None, {'authorizationToken': 'camel-token'},
)


def test_wait_for_build_retries_transient_sandbox_errors(monkeypatch):
class BuildClient(SandboxClient):
def __init__(self):
Expand Down
Loading
Loading