-
Notifications
You must be signed in to change notification settings - Fork 262
feat(sandbox): add sandbox module #465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 9 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
a5adf91
feat(sandbox): add sandbox module
miclle 0638458
feat(sandbox): align runtime APIs with e2b
miclle 1ade2ea
chore(sandbox): remove differences doc from commit
miclle eb025fc
fix(sandbox): honor background command handles
miclle 3162868
feat(sandbox): align more e2b python APIs
miclle 361e2d3
fix(sandbox): honor git credentials and file timeouts
miclle e0deaca
fix(sandbox): address PR feedback
miclle edb32dd
fix(sandbox): harden reviewer feedback paths
miclle d1f007d
fix(sandbox): tighten credential and compat paths
miclle d93e914
fix(sandbox): address latest review feedback
miclle bdba675
fix(sandbox): harden latest review paths
miclle e3f513d
fix(sandbox): close latest review gaps
miclle 083ffde
fix(sandbox): sign saved injection sandbox creates
miclle c8ec04d
fix(sandbox): clean up latest review nits
miclle 774d1b5
fix(sandbox): address expanded review feedback
miclle a1d4377
fix(sandbox): tighten python compatibility feedback
miclle b9a22f6
fix(sandbox): harden git credential cleanup
miclle 0aeef16
fix(sandbox): handle bytes command output
miclle 7674645
fix(sandbox): support api key env fallbacks
miclle 97d3967
fix(sandbox): harden latest review paths
miclle 8627b2b
fix(sandbox): wrap envd transport errors
miclle 89209eb
fix(sandbox): avoid git credential command leaks
miclle 25e769c
fix(sandbox): tighten client edge cases
miclle 0898f5f
fix(sandbox): close paginated review feedback
miclle 850fe56
fix(sandbox): simplify git config helpers
miclle 2d09a65
fix(sandbox): align command stream timeout
miclle ecb581c
fix(sandbox): preserve e2b compatibility edges
miclle 64853e5
fix(sandbox): address review compatibility edges
miclle b50de52
fix(sandbox): clarify option and credential validation
miclle a1e3a11
fix(sandbox): harden background credentials and bytes input
miclle 18579c1
fix(sandbox): simplify stream decoding and cleanup
miclle 66218bb
fix(sandbox): tighten async and timeout edge cases
miclle 6af02ec
fix(sandbox): close command git and template edge cases
miclle 03049b4
fix(sandbox): cover readiness and credential cleanup edges
miclle 59d2d3e
fix(sandbox): avoid credential files and harden polling
miclle 563d356
fix(sandbox): address review edge cases
miclle e4aab28
fix(sandbox): preserve protocol errors and git status parsing
miclle fc22552
fix(sandbox): tighten polling and paginator edges
miclle fcca4e4
fix(sandbox): harden git credentials and config parsing
miclle 90a4882
fix(sandbox): harden text uploads
miclle 17a638d
fix(sandbox): detect relative legacy git config paths
miclle 5fa7701
ci: run public checks for fork pull requests
miclle 666a206
ci: install pip when conda environment lacks it
miclle c0ecff2
test: expand sandbox examples
miclle c198b56
ci: ignore windows mock server cleanup failures
miclle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # Copy this file to .env in the project root before running sandbox examples | ||
| # or sandbox integration tests. | ||
|
|
||
| # Sandbox API key auth. | ||
| QINIU_SANDBOX_API_KEY= | ||
|
|
||
| # Optional custom endpoint. | ||
| # QINIU_SANDBOX_ENDPOINT is the preferred name; QINIU_SANDBOX_API_URL and | ||
| # E2B_API_URL are also accepted by the SDK for compatibility. | ||
| QINIU_SANDBOX_ENDPOINT= | ||
|
|
||
| # Optional template alias or ID. Defaults to base. | ||
| QINIU_SANDBOX_TEMPLATE=base | ||
|
|
||
| # Required only for injection-rule and Kodo resource examples/tests. | ||
| QINIU_SANDBOX_ACCESS_KEY= | ||
| QINIU_SANDBOX_SECRET_KEY= | ||
|
|
||
| # Optional Git remote examples. | ||
| GIT_REPO_URL= | ||
| GIT_USERNAME= | ||
| GIT_PASSWORD= | ||
|
|
||
| # Optional Git repository resource example. | ||
| GITHUB_TOKEN= | ||
| QINIU_SANDBOX_GIT_MOUNT_PATH=/workspace/repo | ||
|
|
||
| # Optional Kodo resource example. | ||
| QINIU_SANDBOX_KODO_BUCKET= | ||
| QINIU_SANDBOX_KODO_MOUNT_PATH=/workspace/kodo | ||
| QINIU_SANDBOX_KODO_PREFIX= | ||
|
|
||
| # Optional request injection examples. | ||
| QINIU_SANDBOX_HTTP_INJECTION_TOKEN=real_token | ||
| QINIU_SANDBOX_OPENAI_API_KEY= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| .DS_Store | ||
| *.swp | ||
| *.pyc | ||
| .env | ||
|
|
||
| *.py[cod] | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import print_function | ||
| import os | ||
|
|
||
| from qiniu.services.sandbox import Sandbox | ||
| from qiniu.services.sandbox.config import ( | ||
| env, | ||
| load_dotenv_if_present, | ||
| required_env, | ||
| sandbox_client, | ||
| sandbox_template, | ||
| ) | ||
|
|
||
|
|
||
| __all__ = [ | ||
| 'cleanup_sandbox', | ||
| 'create_sandbox', | ||
| 'env', | ||
| 'load_example_env', | ||
| 'required_env', | ||
| 'run_example', | ||
| 'sandbox_client', | ||
| 'sandbox_template', | ||
| ] | ||
|
|
||
|
|
||
| def load_example_env(): | ||
| root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) | ||
| load_dotenv_if_present( | ||
| os.path.join(root, '.env'), | ||
| os.path.join(os.getcwd(), '.env'), | ||
| ) | ||
|
|
||
|
|
||
| def create_sandbox(**options): | ||
| client = options.pop('client', None) or sandbox_client() | ||
| template = options.pop('template', sandbox_template()) | ||
| options.setdefault('timeout', 300) | ||
| sandbox = Sandbox.create(template, client=client, **options) | ||
| print('Sandbox created:', sandbox.sandbox_id) | ||
| return sandbox | ||
|
|
||
|
|
||
| def cleanup_sandbox(sandbox): | ||
| if sandbox is None: | ||
| return | ||
| try: | ||
| sandbox.kill() | ||
| print('Sandbox killed:', sandbox.sandbox_id) | ||
| except Exception as err: | ||
| print('Failed to kill sandbox:', sandbox.sandbox_id, err) | ||
|
|
||
|
|
||
| def run_example(fn): | ||
| load_example_env() | ||
| try: | ||
| fn() | ||
| except Exception as err: | ||
| print(err) | ||
| raise |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import print_function | ||
|
|
||
| from qiniu.services.sandbox import Sandbox | ||
| from sandbox_common import ( | ||
| cleanup_sandbox, | ||
| create_sandbox, | ||
| run_example, | ||
| sandbox_client, | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| client = sandbox_client() | ||
| sandbox = create_sandbox( | ||
| client=client, | ||
| timeout=300, | ||
| metadata={'example': 'sandbox_connect'}, | ||
| ) | ||
| try: | ||
| items = Sandbox.list(client=client, limit=10).next_items() | ||
| print('list:', [item.sandbox_id for item in items]) | ||
|
|
||
| connected = Sandbox.connect( | ||
| sandbox.sandbox_id, | ||
| client=client, | ||
| timeout=300, | ||
| ) | ||
| print('connected:', connected.sandbox_id) | ||
| print('envd:', connected.envd_url()) | ||
| print('uptime:', connected.commands.run('uptime').stdout) | ||
| finally: | ||
| cleanup_sandbox(sandbox) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| run_example(main) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import print_function | ||
|
|
||
| from sandbox_common import cleanup_sandbox, create_sandbox, run_example | ||
|
|
||
|
|
||
| def main(): | ||
| sandbox = create_sandbox( | ||
| metadata={'example': 'sandbox_create'}, | ||
| envs={'HELLO': 'qiniu'}, | ||
| ) | ||
| try: | ||
| print('sandbox:', sandbox.sandbox_id) | ||
| result = sandbox.commands.run('printf "$HELLO"') | ||
| print('stdout:', result.stdout) | ||
|
|
||
| sandbox.files.write('/tmp/qiniu.txt', 'hello from qiniu sandbox') | ||
| print('file:', sandbox.files.read_text('/tmp/qiniu.txt')) | ||
| finally: | ||
| cleanup_sandbox(sandbox) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| run_example(main) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import print_function | ||
|
|
||
| import os | ||
| import time | ||
|
|
||
| from sandbox_common import cleanup_sandbox, create_sandbox, run_example | ||
|
|
||
|
|
||
| def assert_git_ok(step, result): | ||
| if result.exit_code != 0: | ||
| raise RuntimeError( | ||
| '{0} failed with exit {1}: {2}'.format( | ||
| step, | ||
| result.exit_code, | ||
| result.stderr or result.stdout, | ||
| ) | ||
| ) | ||
| print(step + ':', result.exit_code) | ||
| return result | ||
|
|
||
|
|
||
| def is_retryable_git_network_error(result): | ||
| message = (result.stderr or result.stdout or result.error or '').lower() | ||
| return result.exit_code != 0 and ( | ||
| 'gnutls' in message or | ||
| 'tls connection' in message or | ||
| 'unable to access' in message or | ||
| 'the remote end hung up unexpectedly' in message | ||
| ) | ||
|
|
||
|
|
||
| def assert_git_network_ok(step, run, attempts=5): | ||
| result = None | ||
| for attempt in range(attempts): | ||
| result = run() | ||
| if result.exit_code == 0: | ||
| print(step + ':', result.exit_code) | ||
| return result | ||
| if not is_retryable_git_network_error(result) or attempt == attempts - 1: | ||
| return assert_git_ok(step, result) | ||
| print('{0}: retry {1}/{2}'.format(step, attempt + 2, attempts)) | ||
| time.sleep(attempt + 1) | ||
| return assert_git_ok(step, result) | ||
|
|
||
|
|
||
| def remote_git_config(): | ||
| repo_url = os.getenv('GIT_REPO_URL') | ||
| username = os.getenv('GIT_USERNAME') | ||
| password = os.getenv('GIT_PASSWORD') or os.getenv('GITHUB_TOKEN') | ||
| if password and not username: | ||
| username = 'x-access-token' | ||
| if not repo_url or not username or not password: | ||
| return None | ||
| return repo_url, username, password | ||
|
|
||
|
|
||
| def run_remote_push_demo(sandbox): | ||
| config = remote_git_config() | ||
| if not config: | ||
| print('remote git push: skipped, GIT_REPO_URL/Git credentials missing') | ||
| return | ||
|
|
||
| repo_url, username, password = config | ||
| branch = 'python-sdk-example-{0}'.format(int(time.time() * 1000)) | ||
| repo_path = '/tmp/qiniu-python-sdk-git/remote' | ||
|
|
||
| assert_git_ok( | ||
| 'git authenticate', | ||
| sandbox.git.dangerously_authenticate(username, password), | ||
| ) | ||
| assert_git_ok( | ||
| 'git http version', | ||
| sandbox.git.set_config( | ||
| None, 'http.version', 'HTTP/1.1', global_config=True), | ||
| ) | ||
| assert_git_network_ok( | ||
| 'git clone remote', | ||
| lambda: ( | ||
| sandbox.commands.run('rm -rf {0}'.format(repo_path)), | ||
| sandbox.git.clone(repo_url, repo_path, depth=1), | ||
| )[1], | ||
|
miclle marked this conversation as resolved.
Outdated
|
||
| ) | ||
| assert_git_ok( | ||
| 'configure remote user', | ||
| sandbox.git.configure_user( | ||
| repo_path, | ||
| 'Qiniu Python SDK', | ||
| 'qiniu-python-sdk@example.com', | ||
| ), | ||
| ) | ||
| assert_git_ok( | ||
| 'checkout remote branch', | ||
| sandbox.git.checkout_branch(repo_path, branch, create=True), | ||
| ) | ||
| sandbox.files.write( | ||
| repo_path + '/python-sdk-example.txt', | ||
| 'qiniu-python-sdk example push {0}\n'.format(branch), | ||
| ) | ||
| assert_git_ok('git add remote', sandbox.git.add(repo_path, all=True)) | ||
| assert_git_ok( | ||
| 'git commit remote', | ||
| sandbox.git.commit(repo_path, 'test: qiniu python sdk example push'), | ||
| ) | ||
| assert_git_network_ok( | ||
| 'git push remote', | ||
| lambda: sandbox.git.push( | ||
| repo_path, | ||
| 'origin', | ||
| 'HEAD:refs/heads/{0}'.format(branch), | ||
| ), | ||
| ) | ||
| print('remote git branch:', branch) | ||
|
|
||
|
|
||
| def main(): | ||
| repo_path = '/tmp/qiniu-python-sdk-git/repo' | ||
| clone_path = '/tmp/qiniu-python-sdk-git/clone' | ||
| sandbox = create_sandbox(timeout=300) | ||
| try: | ||
| sandbox.commands.run( | ||
| 'mkdir -p /tmp/qiniu-python-sdk-git/repo' | ||
| ) | ||
| assert_git_ok('git init', sandbox.git.init(repo_path)) | ||
| assert_git_ok( | ||
| 'configure user', | ||
| sandbox.git.configure_user( | ||
| repo_path, | ||
| 'Sandbox Demo', | ||
| 'sandbox-demo@example.com', | ||
| ), | ||
| ) | ||
| sandbox.files.write(repo_path + '/README.md', '# sandbox git demo\n') | ||
| assert_git_ok('git add', sandbox.git.add(repo_path, all=True)) | ||
| assert_git_ok( | ||
| 'git commit', | ||
| sandbox.git.commit(repo_path, 'feat: initial commit'), | ||
| ) | ||
| assert_git_ok('git clone', sandbox.git.clone(repo_path, clone_path)) | ||
| status = sandbox.git.status(clone_path) | ||
| print('clone status clean:', status.is_clean) | ||
| run_remote_push_demo(sandbox) | ||
| finally: | ||
| cleanup_sandbox(sandbox) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| run_example(main) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import print_function | ||
|
|
||
| from sandbox_common import run_example, sandbox_client | ||
|
|
||
|
|
||
| def main(): | ||
| client = sandbox_client() | ||
| rule = client.create_injection_rule( | ||
| name='python-sdk-example', | ||
| injection={ | ||
| 'type': 'http', | ||
| 'base_url': 'https://api.example.com', | ||
| 'headers': {'X-From-Sandbox': 'qiniu-python-sdk'}, | ||
| }, | ||
| ) | ||
| print('created:', rule) | ||
| rules = client.list_injection_rules() | ||
| print('rules count:', len(rules)) | ||
| rule_id = rule.get('id') or rule.get('ruleID') | ||
| client.delete_injection_rule(rule_id) | ||
| print('deleted:', rule_id) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| run_example(main) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # -*- coding: utf-8 -*- | ||
| from __future__ import print_function | ||
|
|
||
| from sandbox_common import cleanup_sandbox, create_sandbox, run_example | ||
|
|
||
|
|
||
| def main(): | ||
| sandbox = create_sandbox(timeout=300) | ||
| try: | ||
| print('created:', sandbox.sandbox_id) | ||
| sandbox.set_timeout(600) | ||
| sandbox.refresh(duration=600) | ||
| info = sandbox.get_info() | ||
| print('state:', info.get('state')) | ||
| finally: | ||
| cleanup_sandbox(sandbox) | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| run_example(main) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.