image_agnostic.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """This module contains functions for building and managing the agnostic sandbox image.
  2. This WILL BE DEPRECATED when EventStreamRuntime is fully implemented and adopted.
  3. """
  4. import tempfile
  5. import docker
  6. from opendevin.core.logger import opendevin_logger as logger
  7. def generate_dockerfile(base_image: str) -> str:
  8. """Generate the Dockerfile content for the agnostic sandbox image based on user-provided base image.
  9. NOTE: This is only tested on debian yet.
  10. """
  11. # FIXME: Remove the requirement of ssh in future version
  12. dockerfile_content = (
  13. f'FROM {base_image}\n'
  14. 'RUN apt update && apt install -y openssh-server wget sudo\n'
  15. 'RUN mkdir -p -m0755 /var/run/sshd\n'
  16. 'RUN mkdir -p /opendevin && mkdir -p /opendevin/logs && chmod 777 /opendevin/logs\n'
  17. 'RUN echo "" > /opendevin/bash.bashrc\n'
  18. 'RUN if [ ! -d /opendevin/miniforge3 ]; then \\\n'
  19. ' wget --progress=bar:force -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" && \\\n'
  20. ' bash Miniforge3.sh -b -p /opendevin/miniforge3 && \\\n'
  21. ' rm Miniforge3.sh && \\\n'
  22. ' chmod -R g+w /opendevin/miniforge3 && \\\n'
  23. ' bash -c ". /opendevin/miniforge3/etc/profile.d/conda.sh && conda config --set changeps1 False && conda config --append channels conda-forge"; \\\n'
  24. ' fi\n'
  25. 'RUN /opendevin/miniforge3/bin/pip install --upgrade pip\n'
  26. 'RUN /opendevin/miniforge3/bin/pip install jupyterlab notebook jupyter_kernel_gateway flake8\n'
  27. 'RUN /opendevin/miniforge3/bin/pip install python-docx PyPDF2 python-pptx pylatexenc openai\n'
  28. ).strip()
  29. return dockerfile_content
  30. def _build_sandbox_image(
  31. base_image: str, target_image_name: str, docker_client: docker.DockerClient
  32. ):
  33. try:
  34. with tempfile.TemporaryDirectory() as temp_dir:
  35. dockerfile_content = generate_dockerfile(base_image)
  36. logger.info(f'Building agnostic sandbox image: {target_image_name}')
  37. logger.info(
  38. (
  39. f'===== Dockerfile content =====\n'
  40. f'{dockerfile_content}\n'
  41. f'==============================='
  42. )
  43. )
  44. with open(f'{temp_dir}/Dockerfile', 'w') as file:
  45. file.write(dockerfile_content)
  46. api_client = docker_client.api
  47. build_logs = api_client.build(
  48. path=temp_dir, tag=target_image_name, rm=True, decode=True
  49. )
  50. for log in build_logs:
  51. if 'stream' in log:
  52. print(log['stream'].strip())
  53. elif 'error' in log:
  54. logger.error(log['error'].strip())
  55. else:
  56. logger.info(str(log))
  57. logger.info(f'Image {target_image_name} built successfully')
  58. except docker.errors.BuildError as e:
  59. logger.error(f'Sandbox image build failed: {e}')
  60. raise e
  61. except Exception as e:
  62. logger.error(f'An error occurred during sandbox image build: {e}')
  63. raise e
  64. def _get_new_image_name(base_image: str) -> str:
  65. prefix = 'od_sandbox'
  66. if ':' not in base_image:
  67. base_image = base_image + ':latest'
  68. [repo, tag] = base_image.split(':')
  69. repo = repo.replace('/', '___')
  70. return f'{prefix}:{repo}__{tag}'
  71. def get_od_sandbox_image(base_image: str, docker_client: docker.DockerClient) -> str:
  72. """Return the sandbox image name based on user-provided base image.
  73. The returned sandbox image is assumed to contains all the required dependencies for OpenDevin.
  74. If the sandbox image is not found, it will be built.
  75. """
  76. # OpenDevin's offcial sandbox already contains the required dependencies for OpenDevin.
  77. if 'ghcr.io/opendevin/sandbox' in base_image:
  78. return base_image
  79. new_image_name = _get_new_image_name(base_image)
  80. # Detect if the sandbox image is built
  81. images = docker_client.images.list()
  82. for image in images:
  83. if new_image_name in image.tags:
  84. logger.info('Found existing od_sandbox image, reuse:' + new_image_name)
  85. return new_image_name
  86. # If the sandbox image is not found, build it
  87. logger.info(
  88. f'od_sandbox image is not found for {base_image}, will build: {new_image_name}'
  89. )
  90. _build_sandbox_image(base_image, new_image_name, docker_client)
  91. return new_image_name