image_agnostic.py 4.4 KB

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