Non root user (#10155)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
chuckbutkus 2025-08-27 02:23:39 -04:00 committed by GitHub
parent 18b5139237
commit c0bb84dfa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 229 additions and 101 deletions

View File

@ -58,34 +58,34 @@ RUN sed -i 's/^UID_MIN.*/UID_MIN 499/' /etc/login.defs
# Default is 60000, but we've seen up to 200000
RUN sed -i 's/^UID_MAX.*/UID_MAX 1000000/' /etc/login.defs
RUN groupadd --gid $OPENHANDS_USER_ID app
RUN groupadd --gid $OPENHANDS_USER_ID openhands
RUN useradd -l -m -u $OPENHANDS_USER_ID --gid $OPENHANDS_USER_ID -s /bin/bash openhands && \
usermod -aG app openhands && \
usermod -aG openhands openhands && \
usermod -aG sudo openhands && \
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
RUN chown -R openhands:app /app && chmod -R 770 /app
RUN sudo chown -R openhands:app $WORKSPACE_BASE && sudo chmod -R 770 $WORKSPACE_BASE
RUN chown -R openhands:openhands /app && chmod -R 770 /app
RUN sudo chown -R openhands:openhands $WORKSPACE_BASE && sudo chmod -R 770 $WORKSPACE_BASE
USER openhands
ENV VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH" \
PYTHONPATH='/app'
COPY --chown=openhands:app --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY --chown=openhands:openhands --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY --chown=openhands:app --chmod=770 ./microagents ./microagents
COPY --chown=openhands:app --chmod=770 ./openhands ./openhands
COPY --chown=openhands:app --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
COPY --chown=openhands:app pyproject.toml poetry.lock README.md MANIFEST.in LICENSE ./
COPY --chown=openhands:openhands --chmod=770 ./microagents ./microagents
COPY --chown=openhands:openhands --chmod=770 ./openhands ./openhands
COPY --chown=openhands:openhands --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
COPY --chown=openhands:openhands pyproject.toml poetry.lock README.md MANIFEST.in LICENSE ./
# This is run as "openhands" user, and will create __pycache__ with openhands:openhands ownership
RUN python openhands/core/download.py # No-op to download assets
# Add this line to set group ownership of all files/directories not already in "app" group
# openhands:openhands -> openhands:app
RUN find /app \! -group app -exec chgrp app {} +
# openhands:openhands -> openhands:openhands
RUN find /app \! -group openhands -exec chgrp openhands {} +
COPY --chown=openhands:app --chmod=770 --from=frontend-builder /app/build ./frontend/build
COPY --chown=openhands:app --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh
COPY --chown=openhands:openhands --chmod=770 --from=frontend-builder /app/build ./frontend/build
COPY --chown=openhands:openhands --chmod=770 ./containers/app/entrypoint.sh /app/entrypoint.sh
USER root

View File

@ -2,6 +2,8 @@ import os
from pydantic import BaseModel, ConfigDict, Field, ValidationError, model_validator
from openhands.core.logger import openhands_logger as logger
class SandboxConfig(BaseModel):
"""Configuration for the sandbox.
@ -55,6 +57,7 @@ class SandboxConfig(BaseModel):
)
runtime_container_image: str | None = Field(default=None)
user_id: int = Field(default=os.getuid() if hasattr(os, 'getuid') else 1000)
logger.debug(f'SandboxConfig user_id default: {user_id}')
timeout: int = Field(default=120)
remote_runtime_init_timeout: int = Field(default=180)
remote_runtime_api_timeout: int = Field(default=10)

View File

@ -647,7 +647,6 @@ class ActionExecutor:
if __name__ == '__main__':
logger.warning('Starting Action Execution Server')
parser = argparse.ArgumentParser()
parser.add_argument('port', type=int, help='Port to listen on')
parser.add_argument('--working-dir', type=str, help='Working directory')

View File

@ -76,6 +76,7 @@ class RemoteRuntime(ActionExecutionClient):
user_id,
git_provider_tokens,
)
logger.debug(f'RemoteRuntime.init user_id {user_id}')
if self.config.sandbox.api_key is None:
raise ValueError(
'API key is required to use the remote runtime. '

View File

@ -1,4 +1,5 @@
from openhands.core.config import OpenHandsConfig
from openhands.core.logger import openhands_logger as logger
from openhands.runtime.plugins import PluginRequirement
DEFAULT_PYTHON_PREFIX = [
@ -23,6 +24,9 @@ def get_action_execution_server_startup_command(
python_executable: str = 'python',
) -> list[str]:
sandbox_config = app_config.sandbox
logger.debug(f'app_config {vars(app_config)}')
logger.debug(f'sandbox_config {vars(sandbox_config)}')
logger.debug(f'override_user_id {override_user_id}')
# Plugin args
plugin_args = []
@ -39,9 +43,7 @@ def get_action_execution_server_startup_command(
username = override_username or (
'openhands' if app_config.run_as_openhands else 'root'
)
user_id = override_user_id or (
sandbox_config.user_id if app_config.run_as_openhands else 0
)
user_id = override_user_id or (1000 if app_config.run_as_openhands else 0)
base_cmd = [
*python_prefix,
@ -62,5 +64,6 @@ def get_action_execution_server_startup_command(
if not app_config.enable_browser:
base_cmd.append('--no-enable-browser')
logger.debug(f'get_action_execution_server_startup_command: {base_cmd}')
return base_cmd

View File

@ -49,6 +49,61 @@ def init_user_and_working_directory(
if username == os.getenv('USER') and username not in ['root', 'openhands']:
return None
# Skip root since it is already created
if username != 'root':
# Check if the username already exists
logger.debug(f'Attempting to create user `{username}` with UID {user_id}.')
existing_user_id = -1
try:
result = subprocess.run(
f'id -u {username}', shell=True, check=True, capture_output=True
)
existing_user_id = int(result.stdout.decode().strip())
# The user ID already exists, skip setup
if existing_user_id == user_id:
logger.debug(
f'User `{username}` already has the provided UID {user_id}. Skipping user setup.'
)
else:
logger.warning(
f'User `{username}` already exists with UID {existing_user_id}. Skipping user setup.'
)
return existing_user_id
return None
except subprocess.CalledProcessError as e:
# Returncode 1 indicates, that the user does not exist yet
if e.returncode == 1:
logger.debug(
f'User `{username}` does not exist. Proceeding with user creation.'
)
else:
logger.error(
f'Error checking user `{username}`, skipping setup:\n{e}\n'
)
raise
# Add sudoer
sudoer_line = r"echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
output = subprocess.run(sudoer_line, shell=True, capture_output=True)
if output.returncode != 0:
raise RuntimeError(f'Failed to add sudoer: {output.stderr.decode()}')
logger.debug(f'Added sudoer successfully. Output: [{output.stdout.decode()}]')
command = (
f'useradd -rm -d /home/{username} -s /bin/bash '
f'-g root -G sudo -u {user_id} {username}'
)
output = subprocess.run(command, shell=True, capture_output=True)
if output.returncode == 0:
logger.debug(
f'Added user `{username}` successfully with UID {user_id}. Output: [{output.stdout.decode()}]'
)
else:
raise RuntimeError(
f'Failed to create user `{username}` with UID {user_id}. Output: [{output.stderr.decode()}]'
)
# First create the working directory, independent of the user
logger.debug(f'Client working directory: {initial_cwd}')
command = f'umask 002; mkdir -p {initial_cwd}'
@ -64,57 +119,4 @@ def init_user_and_working_directory(
out_str += output.stdout.decode()
logger.debug(f'Created working directory. Output: [{out_str}]')
# Skip root since it is already created
if username == 'root':
return None
# Check if the username already exists
existing_user_id = -1
try:
result = subprocess.run(
f'id -u {username}', shell=True, check=True, capture_output=True
)
existing_user_id = int(result.stdout.decode().strip())
# The user ID already exists, skip setup
if existing_user_id == user_id:
logger.debug(
f'User `{username}` already has the provided UID {user_id}. Skipping user setup.'
)
else:
logger.warning(
f'User `{username}` already exists with UID {existing_user_id}. Skipping user setup.'
)
return existing_user_id
return None
except subprocess.CalledProcessError as e:
# Returncode 1 indicates, that the user does not exist yet
if e.returncode == 1:
logger.debug(
f'User `{username}` does not exist. Proceeding with user creation.'
)
else:
logger.error(f'Error checking user `{username}`, skipping setup:\n{e}\n')
raise
# Add sudoer
sudoer_line = r"echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers"
output = subprocess.run(sudoer_line, shell=True, capture_output=True)
if output.returncode != 0:
raise RuntimeError(f'Failed to add sudoer: {output.stderr.decode()}')
logger.debug(f'Added sudoer successfully. Output: [{output.stdout.decode()}]')
command = (
f'useradd -rm -d /home/{username} -s /bin/bash '
f'-g root -G sudo -u {user_id} {username}'
)
output = subprocess.run(command, shell=True, capture_output=True)
if output.returncode == 0:
logger.debug(
f'Added user `{username}` successfully with UID {user_id}. Output: [{output.stdout.decode()}]'
)
else:
raise RuntimeError(
f'Failed to create user `{username}` with UID {user_id}. Output: [{output.stderr.decode()}]'
)
return None

View File

@ -14,12 +14,16 @@ ENV POETRY_VIRTUALENVS_PATH=/openhands/poetry \
{% macro setup_base_system() %}
# Set PATH early to ensure system commands are available
ENV PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
# Install base system dependencies
{% if (('ubuntu' in base_image) or ('mswebench' in base_image)) %}
RUN apt-get update && \
apt-get install -y --no-install-recommends \
wget curl ca-certificates sudo apt-utils git jq tmux build-essential ripgrep ffmpeg \
coreutils util-linux procps findutils grep sed \
{%- if (base_image.endswith(':latest') or base_image.endswith(':24.04') or ('mswebench' in base_image)) -%}
libgl1 \
{%- else %}
@ -41,6 +45,7 @@ RUN apt-get update && \
RUN apt-get update && \
apt-get install -y --no-install-recommends \
wget curl ca-certificates sudo apt-utils git jq tmux build-essential ripgrep ffmpeg \
coreutils util-linux procps findutils grep sed \
libgl1-mesa-glx \
libasound2-plugins libatomic1 \
# Install Docker dependencies
@ -58,15 +63,30 @@ RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="/openhands/
# Add /openhands/bin to PATH
ENV PATH="/openhands/bin:${PATH}"
# Remove UID 1000 named pn or ubuntu, so the 'openhands' user can be created from ubuntu hosts
# Remove UID 1000 and GID 1000 users/groups that might conflict with openhands user
RUN (if getent passwd 1000 | grep -q pn; then userdel pn; fi) && \
(if getent passwd 1000 | grep -q ubuntu; then userdel ubuntu; fi)
(if getent passwd 1000 | grep -q ubuntu; then userdel ubuntu; fi) && \
(if getent group 1000 | grep -q pn; then groupdel pn; fi) && \
(if getent group 1000 | grep -q ubuntu; then groupdel ubuntu; fi)
# Create openhands group and user
RUN groupadd -g 1000 openhands && \
useradd -u 1000 -g 1000 -m -s /bin/bash openhands && \
usermod -aG sudo openhands && \
echo 'openhands ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
# Set empty password for openhands user to allow passwordless su
passwd -d openhands && \
# Set empty password for root user as well to ensure su works in both directions
passwd -d root && \
# Ensure root can su to openhands without password by configuring PAM
sed -i '/pam_rootok.so/d' /etc/pam.d/su && \
sed -i '1i auth sufficient pam_rootok.so' /etc/pam.d/su
# Create necessary directories
RUN mkdir -p /openhands && \
mkdir -p /openhands/logs && \
mkdir -p /openhands/poetry
mkdir -p /openhands/poetry && \
chown -R openhands:openhands /openhands
# ================================================================
@ -147,14 +167,16 @@ RUN if [ -z "${RELEASE_TAG}" ]; then \
if [ -d "${OPENVSCODE_SERVER_ROOT}" ]; then rm -rf "${OPENVSCODE_SERVER_ROOT}"; fi && \
mv ${RELEASE_TAG}-linux-${arch} ${OPENVSCODE_SERVER_ROOT} && \
cp ${OPENVSCODE_SERVER_ROOT}/bin/remote-cli/openvscode-server ${OPENVSCODE_SERVER_ROOT}/bin/remote-cli/code && \
rm -f ${RELEASE_TAG}-linux-${arch}.tar.gz
rm -f ${RELEASE_TAG}-linux-${arch}.tar.gz && \
chown -R openhands:openhands ${OPENVSCODE_SERVER_ROOT}
{% endmacro %}
{% macro install_vscode_extensions() %}
# Install our custom extension
# Install our custom extensions as openhands user
USER openhands
RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world && \
cp -r /openhands/code/openhands/runtime/utils/vscode-extensions/hello-world/* ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-hello-world/
@ -165,27 +187,72 @@ RUN mkdir -p ${OPENVSCODE_SERVER_ROOT}/extensions/openhands-memory-monitor && \
RUN rm -rf ${OPENVSCODE_SERVER_ROOT}/extensions/{handlebars,pug,json,diff,grunt,ini,npm}
{% endmacro %}
{% macro install_dependencies() %}
# Install all dependencies
{% macro install_dependencies_root() %}
# Install system-level dependencies that require root
USER root
RUN \
{% if enable_browser %}
# Install system dependencies for Playwright (requires root)
apt-get update && \
apt-get install -y --no-install-recommends \
libnss3 libnspr4 libatk-bridge2.0-0 libdrm2 libxkbcommon0 libxcomposite1 \
libxdamage1 libxrandr2 libgbm1 libxss1 && \
# Install libasound2 - try new package name first (Ubuntu 24.04+), fallback to old name
(apt-get install -y --no-install-recommends libasound2t64 || apt-get install -y --no-install-recommends libasound2) && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
# Install Playwright browsers in shared location accessible to all users
export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers && \
mkdir -p /opt/playwright-browsers && \
/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \
# Set proper permissions for shared access
chmod -R 755 /opt/playwright-browsers && \
# Create cache directories and symlinks for both users
mkdir -p /home/openhands/.cache && \
mkdir -p /root/.cache && \
ln -sf /opt/playwright-browsers /home/openhands/.cache/ms-playwright && \
ln -sf /opt/playwright-browsers /root/.cache/ms-playwright && \
chown -h openhands:openhands /home/openhands/.cache/ms-playwright && \
# Set environment variable for all users
echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /etc/environment && \
{% endif %}
# Set environment variables (requires root)
/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \
# Set permissions for shared read-only access
chmod -R 755 /openhands/poetry && \
chmod -R 755 /openhands/micromamba && \
chown -R openhands:openhands /openhands/poetry && \
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
chown -R openhands:openhands /openhands/workspace && \
chown -R openhands:openhands /openhands/micromamba && \
# Ensure PATH includes system binaries early in startup
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"' >> /etc/environment && \
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:$PATH"' >> /etc/bash.bashrc && \
# Set up conda environment activation for all users
echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /etc/bash.bashrc && \
echo 'micromamba activate openhands 2>/dev/null || true' >> /etc/bash.bashrc && \
# Set up environment for root user
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/openhands/micromamba/bin:$PATH"' >> /root/.bashrc && \
echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /root/.bashrc && \
echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /root/.bashrc && \
echo 'micromamba activate openhands 2>/dev/null || true' >> /root/.bashrc && \
# Clean up system packages (requires root)
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
{% endmacro %}
{% macro install_dependencies_user() %}
# Install user-level dependencies as openhands user
WORKDIR /openhands/code
USER openhands
RUN \
/openhands/micromamba/bin/micromamba config set changeps1 False && \
/openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && \
/openhands/micromamba/bin/micromamba run -n openhands poetry env use python3.12 && \
# Install project dependencies
/openhands/micromamba/bin/micromamba run -n openhands poetry install --only main,runtime --no-interaction --no-root && \
# Update and install additional tools
# (There used to be an "apt-get update" here, hopefully we can skip it.)
{% if enable_browser %}/openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \{% endif %}
# Set environment variables
/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print('OH_INTERPRETER_PATH=' + sys.executable)" >> /etc/environment && \
# Set permissions
chmod -R g+rws /openhands/poetry && \
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
# Clean up
# Clean up user caches
/openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . -n && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
/openhands/micromamba/bin/micromamba clean --all
{% endmacro %}
@ -203,7 +270,16 @@ RUN \
RUN mkdir -p /openhands/micromamba/bin && \
/bin/bash -c "PREFIX_LOCATION=/openhands/micromamba BIN_FOLDER=/openhands/micromamba/bin INIT_YES=no CONDA_FORGE_YES=yes $(curl -L https://micro.mamba.pm/install.sh)" && \
/openhands/micromamba/bin/micromamba config remove channels defaults && \
/openhands/micromamba/bin/micromamba config list
/openhands/micromamba/bin/micromamba config list && \
chown -R openhands:openhands /openhands/micromamba && \
# Create read-only shared access to micromamba for all users
# This allows both root and openhands users to access the same packages
# while maintaining security by keeping openhands as the owner
chmod -R 755 /openhands/micromamba && \
# Create a separate writable location for root's micromamba cache/config
mkdir -p /root/.local/share/micromamba && \
# Set up environment variables for system-wide access
echo 'export PATH="/openhands/micromamba/bin:$PATH"' >> /etc/environment
# Create the openhands virtual environment and install poetry and python
RUN /openhands/micromamba/bin/micromamba create -n openhands -y && \
@ -214,40 +290,75 @@ RUN \
if [ -d /openhands/code ]; then rm -rf /openhands/code; fi && \
mkdir -p /openhands/code/openhands && \
touch /openhands/code/openhands/__init__.py && \
chown -R openhands:openhands /openhands/code && \
# Set global git configuration to ensure proper author/committer information
git config --global user.name "openhands" && \
git config --global user.email "openhands@all-hands.dev"
COPY ./code/pyproject.toml ./code/poetry.lock /openhands/code/
COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/
{{ install_dependencies() }}
{{ install_dependencies_user() }}
{{ install_dependencies_root() }}
# ================================================================
# END: Build Runtime Image from Scratch
# ================================================================
{% endif %}
# Ensure openhands user/group and base dirs exist even when not building from scratch
USER root
RUN \
# Ensure group exists (prefer GID 1000 if available)
if ! getent group openhands >/dev/null 2>&1; then \
if getent group 1000 >/dev/null 2>&1; then groupadd openhands; else groupadd -g 1000 openhands; fi; \
fi && \
# Ensure user exists (prefer UID 1000 if available)
if ! id -u openhands >/dev/null 2>&1; then \
if getent passwd 1000 >/dev/null 2>&1; then useradd -m -s /bin/bash -g openhands openhands; else useradd -u 1000 -g openhands -m -s /bin/bash openhands; fi; \
fi && \
# Ensure home and required directories exist before later steps
mkdir -p /home/openhands && \
mkdir -p /openhands && \
mkdir -p $(dirname ${OPENVSCODE_SERVER_ROOT}) && \
# Ensure ownership is correct for all OpenHands paths
chown -R openhands:openhands /home/openhands || true && \
chown -R openhands:openhands /openhands || true
{{ setup_vscode_server() }}
# ================================================================
# Copy Project source files
# ================================================================
RUN if [ -d /openhands/code/openhands ]; then rm -rf /openhands/code/openhands; fi
COPY ./code/pyproject.toml ./code/poetry.lock /openhands/code/
COPY --chown=openhands:openhands ./code/pyproject.toml ./code/poetry.lock /openhands/code/
RUN if [ -d /openhands/code/microagents ]; then rm -rf /openhands/code/microagents; fi
COPY ./code/microagents /openhands/code/microagents
COPY ./code/openhands /openhands/code/openhands
RUN chmod a+rwx /openhands/code/openhands/__init__.py
COPY --chown=openhands:openhands ./code/microagents /openhands/code/microagents
COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands
RUN chmod a+rwx /openhands/code/openhands/__init__.py && \
chown -R openhands:openhands /openhands/code
# ================================================================
# END: Build from versioned image
# ================================================================
{% if build_from_versioned %}
{{ install_dependencies() }}
{{ install_dependencies_user() }}
{{ install_dependencies_root() }}
{{ install_vscode_extensions() }}
{% endif %}
# Install extra dependencies if specified
{% if extra_deps %}RUN {{ extra_deps }} {% endif %}
# Install extra dependencies if specified (as openhands user)
{% if extra_deps %}
USER openhands
RUN {{ extra_deps }}
{% endif %}
# Set up environment for openhands user
USER root
RUN \
# Set up environment for openhands user
echo 'export PATH="/usr/bin:/bin:/usr/sbin:/sbin:/openhands/micromamba/bin:$PATH"' >> /home/openhands/.bashrc && \
echo 'export PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers' >> /home/openhands/.bashrc && \
echo 'eval "$(/openhands/micromamba/bin/micromamba shell hook --shell bash)"' >> /home/openhands/.bashrc && \
echo 'micromamba activate openhands 2>/dev/null || true' >> /home/openhands/.bashrc && \
chown openhands:openhands /home/openhands/.bashrc

View File

@ -166,7 +166,10 @@ def test_generate_dockerfile_build_from_scratch():
assert 'python=3.12' in dockerfile_content
# Check the update command
assert 'COPY ./code/openhands /openhands/code/openhands' in dockerfile_content
assert (
'COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands'
in dockerfile_content
)
assert (
'/openhands/micromamba/bin/micromamba run -n openhands poetry install'
in dockerfile_content
@ -188,7 +191,10 @@ def test_generate_dockerfile_build_from_lock():
assert 'poetry install' not in dockerfile_content
# These update commands SHOULD still in the dockerfile
assert 'COPY ./code/openhands /openhands/code/openhands' in dockerfile_content
assert (
'COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands'
in dockerfile_content
)
def test_generate_dockerfile_build_from_versioned():
@ -206,7 +212,10 @@ def test_generate_dockerfile_build_from_versioned():
# this SHOULD exist when build from versioned
assert 'poetry install' in dockerfile_content
assert 'COPY ./code/openhands /openhands/code/openhands' in dockerfile_content
assert (
'COPY --chown=openhands:openhands ./code/openhands /openhands/code/openhands'
in dockerfile_content
)
def test_get_runtime_image_repo_and_tag_eventstream():