diff --git a/.changeset/empty-wasps-count.md b/.changeset/empty-wasps-count.md new file mode 100644 index 00000000..019c88f4 --- /dev/null +++ b/.changeset/empty-wasps-count.md @@ -0,0 +1,5 @@ +--- +'@e2b/code-interpreter-template': minor +--- + +updated template to new SDK diff --git a/.github/workflows/build_test_template.yml b/.github/workflows/build_test_template.yml index e8d6edb2..3f39bc41 100644 --- a/.github/workflows/build_test_template.yml +++ b/.github/workflows/build_test_template.yml @@ -3,7 +3,7 @@ name: Build Template on: workflow_call: secrets: - E2B_TESTS_ACCESS_TOKEN: + E2B_API_KEY: required: true inputs: E2B_DOMAIN: @@ -22,7 +22,7 @@ jobs: name: Build E2B Template runs-on: ubuntu-latest outputs: - template_id: ${{ steps.build-template.outputs.template_id }} + template_id: ${{ steps.generate-template-id.outputs.template_id }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -34,22 +34,27 @@ jobs: echo "Version: $VERSION" sed -i "s/e2b_charts/e2b_charts==${VERSION}/g" requirements.txt - - name: Install E2B CLI - run: npm install -g @e2b/cli + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - name: Install development dependencies + working-directory: ./template + run: pip install -r requirements-dev.txt + + - name: Generate Template ID + id: generate-template-id + run: | + E2B_TESTS_TEMPLATE=e2b-code-interpreter-ci-$(uuidgen) + echo "Generated Template ID: $E2B_TESTS_TEMPLATE" + echo "template_id=$E2B_TESTS_TEMPLATE" >> $GITHUB_OUTPUT - name: Build E2B template id: build-template - run: | - rm -f e2b.toml - e2b template build --memory-mb 1024 -c "/root/.jupyter/start-up.sh" -d "Dockerfile" - TEMPLATE_ID=$(grep "template_id" e2b.toml | cut -d '"' -f 2) - echo "Captured Template ID: $TEMPLATE_ID" - echo "template_id=$TEMPLATE_ID" >> $GITHUB_OUTPUT working-directory: ./template + run: | + python build_ci.py env: - E2B_ACCESS_TOKEN: ${{ secrets.E2B_TESTS_ACCESS_TOKEN }} + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} E2B_DOMAIN: ${{ inputs.E2B_DOMAIN }} - - - name: Output template ID - run: | - echo "Template ID from step output: ${{ steps.build-template.outputs.template_id }}" + E2B_TESTS_TEMPLATE: ${{ steps.generate-template-id.outputs.template_id }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 5ea47773..7ed194c9 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -17,7 +17,7 @@ jobs: build-template: uses: ./.github/workflows/build_test_template.yml secrets: - E2B_TESTS_ACCESS_TOKEN: ${{ secrets.E2B_TESTS_ACCESS_TOKEN }} + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} with: E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} js-sdk: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ef0ed82..06a97a21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -200,13 +200,13 @@ jobs: sed -i "s/e2b_charts/e2b_charts==${VERSION}/g" requirements.txt - - name: Build Docker image - uses: docker/build-push-action@v6 - with: - context: ./template - push: true - platforms: linux/amd64 - tags: ${{ secrets.DOCKERHUB_USERNAME }}/code-interpreter:latest + - name: Build and push to DockerHub + working-directory: ./template + run: | + python build_docker.py | docker buildx build \ + --platform linux/amd64 \ + --push \ + --tag ${{ secrets.DOCKERHUB_USERNAME }}/code-interpreter:latest -f - . build-template: name: Build E2B template @@ -219,14 +219,22 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install E2B CLI - run: npm install -g @e2b/cli + - uses: actions/setup-python@v6 + with: + python-version: '3.13' - - name: Build e2b - run: e2b template build + - name: Install development dependencies working-directory: ./template + run: pip install -r requirements-dev.txt + + - name: Build E2B template + id: build-template + working-directory: ./template + run: | + python build_prod.py env: - E2B_ACCESS_TOKEN: ${{ secrets.E2B_ACCESS_TOKEN }} + E2B_API_KEY: ${{ secrets.E2B_API_KEY }} + E2B_DOMAIN: ${{ vars.E2B_DOMAIN }} python-tests: name: Python Tests diff --git a/Makefile b/Makefile index db252514..99efa3b8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ start-template-server: - docker run --rm -e E2B_LOCAL=true -p 49999:49999 -it $$(docker build . -q -f ./template/test.Dockerfile) + docker run --rm -e E2B_LOCAL=true -p 49999:49999 -it $$(python template/build_docker.py | docker build -q ./template -f -) kill-template-server: docker kill $(shell docker ps --filter expose=49999 --format {{.ID}}) diff --git a/template/Dockerfile b/template/Dockerfile deleted file mode 100644 index 814c14c0..00000000 --- a/template/Dockerfile +++ /dev/null @@ -1,74 +0,0 @@ -FROM python:3.12 - -RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-install-recommends \ - build-essential curl git util-linux jq sudo fonts-noto-cjk r-base - -# Install Node.js 20.x from NodeSource -RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ - apt-get install -y nodejs - -ENV PIP_DEFAULT_TIMEOUT=100 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PIP_NO_CACHE_DIR=1 \ - JUPYTER_CONFIG_PATH="/root/.jupyter" \ - IPYTHON_CONFIG_PATH="/root/.ipython" \ - SERVER_PATH="/root/.server" \ - JAVA_HOME=/opt/java/openjdk - -# Install Jupyter -COPY ./requirements.txt requirements.txt -RUN pip install --no-cache-dir -r requirements.txt && ipython kernel install --name "python3" --user - -# R Kernel -RUN R -e "install.packages('IRkernel', repos='https://cloud.r-project.org')" -RUN R -e "IRkernel::installspec(user = FALSE, name = 'r', displayname = 'R')" - -# Javascript Kernel -RUN npm install -g --unsafe-perm git+https://github.com/e2b-dev/ijavascript.git -RUN ijsinstall --install=global - -# Deno Kernel -COPY --from=denoland/deno:bin-2.0.4 /deno /usr/bin/deno -RUN chmod +x /usr/bin/deno -RUN deno jupyter --unstable --install -COPY ./deno.json /root/.local/share/jupyter/kernels/deno/kernel.json - -# Bash Kernel -RUN pip install bash_kernel -RUN python -m bash_kernel.install - -# Create separate virtual environment for server -RUN python -m venv $SERVER_PATH/.venv - -# Copy server and its requirements -RUN mkdir -p $SERVER_PATH/ -COPY ./server/requirements.txt $SERVER_PATH -RUN $SERVER_PATH/.venv/bin/pip install --no-cache-dir -r $SERVER_PATH/requirements.txt -COPY ./server $SERVER_PATH - -# Copy matplotlibrc -COPY matplotlibrc /root/.config/matplotlib/.matplotlibrc - -# Copy Jupyter configuration -COPY ./start-up.sh $JUPYTER_CONFIG_PATH/ -RUN chmod +x $JUPYTER_CONFIG_PATH/start-up.sh - -COPY ./jupyter_server_config.py $JUPYTER_CONFIG_PATH/ - -RUN mkdir -p $IPYTHON_CONFIG_PATH/profile_default -COPY ipython_kernel_config.py $IPYTHON_CONFIG_PATH/profile_default/ - -RUN mkdir -p $IPYTHON_CONFIG_PATH/profile_default/startup -COPY startup_scripts/* $IPYTHON_CONFIG_PATH/profile_default/startup - - -COPY --from=eclipse-temurin:11-jdk $JAVA_HOME $JAVA_HOME -RUN ln -s ${JAVA_HOME}/bin/java /usr/bin/java - -# Java Kernel -RUN wget https://github.com/SpencerPark/IJava/releases/download/v1.3.0/ijava-1.3.0.zip && \ - unzip ijava-1.3.0.zip && \ - python install.py --sys-prefix - -# Setup entrypoint for local development -ENTRYPOINT $JUPYTER_CONFIG_PATH/start-up.sh diff --git a/template/README.md b/template/README.md index c1103acd..19039fc4 100644 --- a/template/README.md +++ b/template/README.md @@ -1,45 +1,57 @@ # Using custom sandbox with Code Interpreter SDK -If you want to customize the Code Interprerter sandbox (e.g.: add a preinstalled package) you can do that by using a [custom sandbox template](https://e2b.dev/docs/sandbox-template). - +If you want to customize the Code Interpreter sandbox (e.g.: add a preinstalled package) you can do that by creating a [custom sandbox template](https://e2b.dev/docs/template/quickstart). ## Step-by-step guide -1. Create custom sandbox by following [this guide](https://e2b.dev/docs/sandbox-template) -2. Use prebuilt [E2B Code Interpreter image](https://hub.docker.com/r/e2bdev/code-interpreter) by replacing the `FROM` command in your `e2b.Dockerfile` with following +1. Install E2B SDK + +``` +pip install e2b dotenv +``` + +2. Create a custom sandbox template: + +**template.py** + +```python +from e2b import Template + +template = Template().from_template("code-interpreter-v1") +``` + +3. Create a build script: - ```Dockerfile - FROM e2bdev/code-interpreter:latest - ``` +**build.py** -3. Copy [`start-up.sh`](./start-up.sh) to the same directory where's your `e2b.toml` +```python +from dotenv import load_dotenv +from .template import template +from e2b import Template, default_build_logger -4. Run the following in the same directory where's your `e2b.toml` - ```sh - e2b template build -c "/root/.jupyter/start-up.sh" - ``` +load_dotenv() -5. Use your custom sandbox with Code Interpreter SDK +Template.build( + template, + alias="code-interpreter-custom", + cpu_count=2, + memory_mb=2048, + on_build_logs=default_build_logger(), +) +``` - **Python** - ```python - from e2b_code_interpreter import Sandbox - sandbox = Sandbox.create(template="your-custom-sandbox-name") - execution = sandbox.run_code("print('hello')") - sandbox.kill() +3. Build the template: - # Or you can use `with` which handles closing the sandbox for you - with Sandbox.create(template="your-custom-sandbox-name") as sandbox: - execution = sandbox.run_code("print('hello')") - ``` - +``` +python build.py +``` - **JavaScript/TypeScript** +4. Use the custom template: - ```js - import {Sandbox} from '@e2b/code-interpreter' +```python +from e2b import Sandbox -const sandbox = await Sandbox.create({template: 'your-custom-sandbox-name'}) -const execution = await sandbox.runCode('print("hello")') -await sandbox.kill() - ``` +sbx = Sandbox.create(template="code-interpreter-custom") +execution = sbx.run_code("print('Hello, World!')") +print(execution.logs.stdout) +``` diff --git a/template/build_ci.py b/template/build_ci.py new file mode 100644 index 00000000..7455836d --- /dev/null +++ b/template/build_ci.py @@ -0,0 +1,11 @@ +import os +from e2b import Template, default_build_logger +from template import make_template + +Template.build( + make_template(set_user_workdir=True), + alias=os.environ["E2B_TESTS_TEMPLATE"], + cpu_count=2, + memory_mb=2048, + on_build_logs=default_build_logger(), +) diff --git a/template/build_docker.py b/template/build_docker.py new file mode 100644 index 00000000..17c17098 --- /dev/null +++ b/template/build_docker.py @@ -0,0 +1,5 @@ +from template import make_template +from e2b import Template + +tmp = make_template(kernels=["python", "javascript"]) +print(Template.to_dockerfile(tmp)) diff --git a/template/build_prod.py b/template/build_prod.py new file mode 100644 index 00000000..d88c447d --- /dev/null +++ b/template/build_prod.py @@ -0,0 +1,13 @@ +from dotenv import load_dotenv +from e2b import Template, default_build_logger +from template import make_template + +load_dotenv() + +Template.build( + make_template(set_user_workdir=True), + alias="code-interpreter-v1", + cpu_count=2, + memory_mb=2048, + on_build_logs=default_build_logger(), +) diff --git a/template/build_test.py b/template/build_test.py new file mode 100644 index 00000000..2e228785 --- /dev/null +++ b/template/build_test.py @@ -0,0 +1,13 @@ +from dotenv import load_dotenv +from e2b import Template, default_build_logger +from template import make_template + +load_dotenv() + +Template.build( + make_template(kernels=["python", "javascript"], set_user_workdir=True), + alias="code-interpreter-dev", + cpu_count=1, + memory_mb=1024, + on_build_logs=default_build_logger(min_level="debug"), +) diff --git a/template/e2b.Dockerfile b/template/e2b.Dockerfile deleted file mode 100644 index 47fb7712..00000000 --- a/template/e2b.Dockerfile +++ /dev/null @@ -1 +0,0 @@ -FROM e2bdev/code-interpreter:latest diff --git a/template/e2b.toml b/template/e2b.toml deleted file mode 100644 index 1eb93ae1..00000000 --- a/template/e2b.toml +++ /dev/null @@ -1,18 +0,0 @@ -# This is a config for E2B sandbox template. -# You can use template ID (nlhz8vlwyupq845jsdg9) or template name (code-interpreter-v1) to create a sandbox: - -# Python SDK -# from e2b import Sandbox, AsyncSandbox -# sandbox = Sandbox.create("code-interpreter-v1") # Sync sandbox -# sandbox = await AsyncSandbox.create("code-interpreter-v1") # Async sandbox - -# JS SDK -# import { Sandbox } from 'e2b' -# const sandbox = await Sandbox.create('code-interpreter-v1') - -team_id = "460355b3-4f64-48f9-9a16-4442817f79f5" -memory_mb = 1_024 -start_cmd = "/root/.jupyter/start-up.sh" -dockerfile = "e2b.Dockerfile" -template_name = "code-interpreter-v1" -template_id = "nlhz8vlwyupq845jsdg9" diff --git a/template/requirements-dev.txt b/template/requirements-dev.txt new file mode 100644 index 00000000..16a0115f --- /dev/null +++ b/template/requirements-dev.txt @@ -0,0 +1,2 @@ +e2b==2.6.0 +python-dotenv==1.2.1 \ No newline at end of file diff --git a/template/start-up.sh b/template/start-up.sh old mode 100644 new mode 100755 index 5d6223ff..81375084 --- a/template/start-up.sh +++ b/template/start-up.sh @@ -13,10 +13,10 @@ function start_jupyter_server() { response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8888/api/status") done - cd /root/.server/ - /root/.server/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 49999 --workers 1 --no-access-log --no-use-colors --timeout-keep-alive 640 + cd /.server/ + .venv/bin/uvicorn main:app --host 0.0.0.0 --port 49999 --workers 1 --no-access-log --no-use-colors --timeout-keep-alive 640 } echo "Starting Code Interpreter server..." start_jupyter_server & -MATPLOTLIBRC=/root/.config/matplotlib/.matplotlibrc jupyter server --IdentityProvider.token="" >/dev/null 2>&1 +MATPLOTLIBRC=.config/matplotlib/.matplotlibrc jupyter server --IdentityProvider.token="" >/dev/null 2>&1 diff --git a/template/template.py b/template/template.py new file mode 100644 index 00000000..36234506 --- /dev/null +++ b/template/template.py @@ -0,0 +1,136 @@ +from e2b import Template, wait_for_url + + +def make_template( + kernels: list[str] = ["python", "r", "javascript", "deno", "bash", "java"], + set_user_workdir: bool = False, +): + enabled_kernels = set(["python", "javascript"] + kernels) + # Start with base template + template = ( + Template() + .from_image("python:3.12") + .set_user("root") + .set_workdir("/") + .set_envs( + { + "PIP_DEFAULT_TIMEOUT": "100", + "PIP_DISABLE_PIP_VERSION_CHECK": "1", + "PIP_NO_CACHE_DIR": "1", + "JUPYTER_CONFIG_PATH": ".jupyter", + "IPYTHON_CONFIG_PATH": ".ipython", + "SERVER_PATH": ".server", + "JAVA_VERSION": "11", + "JAVA_HOME": "/usr/lib/jvm/jdk-${JAVA_VERSION}", + "IJAVA_VERSION": "1.3.0", + "DENO_INSTALL": "/opt/deno", + "DENO_VERSION": "v2.4.0", + "R_VERSION": "4.5.*", + } + ) + .apt_install( + [ + "build-essential", + "curl", + "git", + "util-linux", + "jq", + "sudo", + "fonts-noto-cjk", + "ca-certificates", + ] + ) + .run_cmd("curl -fsSL https://deb.nodesource.com/setup_20.x | bash -") + .apt_install("nodejs") + .copy("requirements.txt", "requirements.txt") + .pip_install("--no-cache-dir -r requirements.txt") + ) + + if "python" in enabled_kernels: + template = template.run_cmd("ipython kernel install --name 'python3' --user") + + # Install R Kernel if requested + if "r" in enabled_kernels: + template = ( + template.run_cmd( + [ + "sudo gpg --keyserver keyserver.ubuntu.com --recv-key 95C0FAF38DB3CCAD0C080A7BDC78B2DDEABC47B7", + "sudo gpg --armor --export 95C0FAF38DB3CCAD0C080A7BDC78B2DDEABC47B7 | sudo tee /etc/apt/trusted.gpg.d/cran_debian_key.asc", + 'echo "deb https://cloud.r-project.org/bin/linux/debian trixie-cran40/" | sudo tee /etc/apt/sources.list.d/cran.list', + ] + ) + .apt_install("r-base=${R_VERSION} r-base-dev") + .run_cmd( + [ + "R -e \"install.packages('IRkernel', repos='https://cloud.r-project.org')\"", + "R -e \"IRkernel::installspec(user = FALSE, name = 'r', displayname = 'R')\"", + ] + ) + ) + + # Install JavaScript Kernel if requested + if "javascript" in enabled_kernels: + template = template.npm_install( + "--unsafe-perm git+https://github.com/e2b-dev/ijavascript.git", + g=True, + ).run_cmd("ijsinstall --install=global") + + # Install Deno Kernel if requested + if "deno" in enabled_kernels: + template = template.run_cmd( + [ + "curl -fsSL https://deno.land/install.sh | sh -s ${DENO_VERSION}", + "PATH=$DENO_INSTALL/bin:$PATH", + "deno jupyter --unstable --install", + ] + ).copy("deno.json", ".local/share/jupyter/kernels/deno/kernel.json") + + # Install Bash Kernel if requested + if "bash" in enabled_kernels: + template = template.pip_install("bash_kernel").run_cmd( + "python -m bash_kernel.install" + ) + + # Install Java and Java Kernel if requested + if "java" in enabled_kernels: + template = template.run_cmd( + [ + "mkdir -p /usr/lib/jvm", + "curl -fsSL https://download.java.net/java/ga/jdk${JAVA_VERSION}/openjdk-${JAVA_VERSION}_linux-x64_bin.tar.gz | tar -xz -C /usr/lib/jvm", + "update-alternatives --install /usr/bin/java java /usr/lib/jvm/jdk-${JAVA_VERSION}/bin/java 1", + "update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/jdk-${JAVA_VERSION}/bin/javac 1", + "wget https://github.com/SpencerPark/IJava/releases/download/v${IJAVA_VERSION}/ijava-${IJAVA_VERSION}.zip", + "unzip ijava-${IJAVA_VERSION}.zip", + "python install.py --sys-prefix", + ] + ) + + # Common setup steps (always run) + template = ( + template + # Create server virtual environment + .copy("server", ".server") + .run_cmd("python -m venv .server/.venv") + # Copy and install server requirements + .run_cmd( + ".server/.venv/bin/pip install --no-cache-dir -r .server/requirements.txt" + ) + ) + + if set_user_workdir: + template = template.set_user("user").set_workdir("/home/user") + + # Copy configuration files + template = ( + template.copy("matplotlibrc", ".config/matplotlib/.matplotlibrc") + .copy("start-up.sh", ".jupyter/start-up.sh") + .run_cmd("chmod +x .jupyter/start-up.sh") + .copy("jupyter_server_config.py", ".jupyter/") + .make_dir(".ipython/profile_default/startup") + .copy("ipython_kernel_config.py", ".ipython/profile_default/") + .copy("startup_scripts", ".ipython/profile_default/startup") + ) + + return template.set_start_cmd( + ".jupyter/start-up.sh", wait_for_url("http://localhost:49999/health") + ) diff --git a/template/test.Dockerfile b/template/test.Dockerfile deleted file mode 100644 index 14eb0005..00000000 --- a/template/test.Dockerfile +++ /dev/null @@ -1,63 +0,0 @@ -FROM python:3.12 - -ENV JAVA_HOME=/opt/java/openjdk -COPY --from=eclipse-temurin:11-jdk $JAVA_HOME $JAVA_HOME -ENV PATH="${JAVA_HOME}/bin:${PATH}" - -RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y --no-install-recommends \ - build-essential curl git util-linux jq sudo fonts-noto-cjk - -# Install Node.js 20.x from NodeSource -RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \ - apt-get install -y nodejs - -ENV PIP_DEFAULT_TIMEOUT=100 \ - PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PIP_NO_CACHE_DIR=1 \ - JUPYTER_CONFIG_PATH="/root/.jupyter" \ - IPYTHON_CONFIG_PATH="/root/.ipython" \ - SERVER_PATH="/root/.server" - -# Install Jupyter -COPY ./template/requirements.txt requirements.txt -RUN pip install --no-cache-dir -r requirements.txt && ipython kernel install --name "python3" --user - -# Javascript Kernel -RUN npm install -g --unsafe-perm git+https://github.com/e2b-dev/ijavascript.git -RUN ijsinstall --install=global - -# Deno Kernel -COPY --from=denoland/deno:bin-2.0.4 /deno /usr/bin/deno -RUN chmod +x /usr/bin/deno -RUN deno jupyter --unstable --install -COPY ./template/deno.json /root/.local/share/jupyter/kernels/deno/kernel.json - -# Create separate virtual environment for server -RUN python -m venv $SERVER_PATH/.venv - -# Copy server and its requirements -RUN mkdir -p $SERVER_PATH/ -COPY ./template/server/requirements.txt $SERVER_PATH -RUN $SERVER_PATH/.venv/bin/pip install --no-cache-dir -r $SERVER_PATH/requirements.txt -COPY ./template/server $SERVER_PATH - -# Copy matplotlibrc -COPY ./template/matplotlibrc /root/.config/matplotlib/matplotlibrc - -# Copy Jupyter configuration -COPY ./template/start-up.sh $JUPYTER_CONFIG_PATH/ -RUN chmod +x $JUPYTER_CONFIG_PATH/start-up.sh - -COPY ./template/jupyter_server_config.py $JUPYTER_CONFIG_PATH/ - -RUN mkdir -p $IPYTHON_CONFIG_PATH/profile_default -COPY ./template/ipython_kernel_config.py $IPYTHON_CONFIG_PATH/profile_default/ - -RUN mkdir -p $IPYTHON_CONFIG_PATH/profile_default/startup -COPY ./template/startup_scripts/* $IPYTHON_CONFIG_PATH/profile_default/startup - -# Setup entrypoint for local development -WORKDIR /home/user -COPY ./chart_data_extractor ./chart_data_extractor -RUN pip install -e ./chart_data_extractor -ENTRYPOINT $JUPYTER_CONFIG_PATH/start-up.sh