1
- import os
2
- import shutil
1
+ """
2
+ This module contains functions for building and managing the agnostic sandbox image.
3
+
4
+ This WILL BE DEPRECATED when EventStreamRuntime is fully implemented and adopted.
5
+ """
6
+
3
7
import tempfile
4
8
5
9
import docker
6
10
7
11
from opendevin .core .logger import opendevin_logger as logger
8
12
9
- from .source import create_project_source_dist
10
-
11
13
12
14
def generate_dockerfile (base_image : str ) -> str :
13
15
"""
@@ -36,122 +38,29 @@ def generate_dockerfile(base_image: str) -> str:
36
38
return dockerfile_content
37
39
38
40
39
- def generate_dockerfile_for_eventstream_runtime (
40
- base_image : str , temp_dir : str , skip_init : bool = False
41
- ) -> str :
42
- """
43
- Generate the Dockerfile content for the eventstream runtime image based on user-provided base image.
44
-
45
- NOTE: This is only tested on debian yet.
46
- """
47
- if skip_init :
48
- dockerfile_content = f'FROM { base_image } \n '
49
- else :
50
- dockerfile_content = (
51
- f'FROM { base_image } \n '
52
- # FIXME: make this more generic / cross-platform
53
- 'RUN apt update && apt install -y wget sudo\n '
54
- 'RUN apt-get update && apt-get install -y libgl1-mesa-glx\n ' # Extra dependency for OpenCV
55
- 'RUN mkdir -p /opendevin && mkdir -p /opendevin/logs && chmod 777 /opendevin/logs\n '
56
- 'RUN echo "" > /opendevin/bash.bashrc\n '
57
- 'RUN if [ ! -d /opendevin/miniforge3 ]; then \\ \n '
58
- ' wget --progress=bar:force -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" && \\ \n '
59
- ' bash Miniforge3.sh -b -p /opendevin/miniforge3 && \\ \n '
60
- ' rm Miniforge3.sh && \\ \n '
61
- ' chmod -R g+w /opendevin/miniforge3 && \\ \n '
62
- ' bash -c ". /opendevin/miniforge3/etc/profile.d/conda.sh && conda config --set changeps1 False && conda config --append channels conda-forge"; \\ \n '
63
- ' fi\n '
64
- 'RUN /opendevin/miniforge3/bin/mamba install python=3.11 -y\n '
65
- 'RUN /opendevin/miniforge3/bin/mamba install conda-forge::poetry -y\n '
66
- )
67
-
68
- tarball_path = create_project_source_dist ()
69
- filename = os .path .basename (tarball_path )
70
- filename = filename .removesuffix ('.tar.gz' )
71
-
72
- # move the tarball to temp_dir
73
- _res = shutil .copy (tarball_path , os .path .join (temp_dir , 'project.tar.gz' ))
74
- if _res :
75
- os .remove (tarball_path )
76
- logger .info (
77
- f'Source distribution moved to { os .path .join (temp_dir , "project.tar.gz" )} '
78
- )
79
-
80
- # Copy the project directory to the container
81
- dockerfile_content += 'COPY project.tar.gz /opendevin\n '
82
- # remove /opendevin/code if it exists
83
- dockerfile_content += (
84
- 'RUN if [ -d /opendevin/code ]; then rm -rf /opendevin/code; fi\n '
85
- )
86
- # unzip the tarball to /opendevin/code
87
- dockerfile_content += (
88
- 'RUN cd /opendevin && tar -xzvf project.tar.gz && rm project.tar.gz\n '
89
- )
90
- dockerfile_content += f'RUN mv /opendevin/{ filename } /opendevin/code\n '
91
- # install (or update) the dependencies
92
- dockerfile_content += (
93
- 'RUN cd /opendevin/code && '
94
- '/opendevin/miniforge3/bin/mamba run -n base poetry env use python3.11 && '
95
- '/opendevin/miniforge3/bin/mamba run -n base poetry install\n '
96
- # for browser (update if needed)
97
- 'RUN apt-get update && cd /opendevin/code && /opendevin/miniforge3/bin/mamba run -n base poetry run playwright install --with-deps chromium\n '
98
- )
99
- return dockerfile_content
100
-
101
-
102
41
def _build_sandbox_image (
103
- base_image : str ,
104
- target_image_name : str ,
105
- docker_client : docker .DockerClient ,
106
- eventstream_runtime : bool = False ,
107
- skip_init : bool = False ,
42
+ base_image : str , target_image_name : str , docker_client : docker .DockerClient
108
43
):
109
44
try :
110
45
with tempfile .TemporaryDirectory () as temp_dir :
111
- if eventstream_runtime :
112
- dockerfile_content = generate_dockerfile_for_eventstream_runtime (
113
- base_image , temp_dir , skip_init = skip_init
114
- )
115
- else :
116
- dockerfile_content = generate_dockerfile (base_image )
46
+ dockerfile_content = generate_dockerfile (base_image )
117
47
118
- if skip_init :
119
- logger .info (
120
- f'Reusing existing od_sandbox image [{ target_image_name } ] but will update the source code in it.'
121
- )
122
- logger .info (
123
- (
124
- f'===== Dockerfile content =====\n '
125
- f'{ dockerfile_content } \n '
126
- f'==============================='
127
- )
128
- )
129
- else :
130
- logger .info (f'Building agnostic sandbox image: { target_image_name } ' )
131
- logger .info (
132
- (
133
- f'===== Dockerfile content =====\n '
134
- f'{ dockerfile_content } \n '
135
- f'==============================='
136
- )
48
+ logger .info (f'Building agnostic sandbox image: { target_image_name } ' )
49
+ logger .info (
50
+ (
51
+ f'===== Dockerfile content =====\n '
52
+ f'{ dockerfile_content } \n '
53
+ f'==============================='
137
54
)
55
+ )
138
56
with open (f'{ temp_dir } /Dockerfile' , 'w' ) as file :
139
57
file .write (dockerfile_content )
140
58
141
59
api_client = docker_client .api
142
60
build_logs = api_client .build (
143
- path = temp_dir ,
144
- tag = target_image_name ,
145
- rm = True ,
146
- decode = True ,
147
- # do not use cache when skip_init is True (i.e., when we want to update the source code in the existing image)
148
- nocache = skip_init ,
61
+ path = temp_dir , tag = target_image_name , rm = True , decode = True
149
62
)
150
63
151
- if skip_init :
152
- logger .info (
153
- f'Rebuilding existing od_sandbox image [{ target_image_name } ] to update the source code.'
154
- )
155
64
for log in build_logs :
156
65
if 'stream' in log :
157
66
print (log ['stream' ].strip ())
@@ -169,14 +78,8 @@ def _build_sandbox_image(
169
78
raise e
170
79
171
80
172
- def _get_new_image_name (
173
- base_image : str , is_eventstream_runtime : bool , dev_mode : bool = False
174
- ) -> str :
81
+ def _get_new_image_name (base_image : str ) -> str :
175
82
prefix = 'od_sandbox'
176
- if is_eventstream_runtime :
177
- prefix = 'od_eventstream_runtime'
178
- if dev_mode :
179
- prefix += '_dev'
180
83
if ':' not in base_image :
181
84
base_image = base_image + ':latest'
182
85
@@ -185,11 +88,7 @@ def _get_new_image_name(
185
88
return f'{ prefix } :{ repo } __{ tag } '
186
89
187
90
188
- def get_od_sandbox_image (
189
- base_image : str ,
190
- docker_client : docker .DockerClient ,
191
- is_eventstream_runtime : bool = False ,
192
- ) -> str :
91
+ def get_od_sandbox_image (base_image : str , docker_client : docker .DockerClient ) -> str :
193
92
"""Return the sandbox image name based on user-provided base image.
194
93
195
94
The returned sandbox image is assumed to contains all the required dependencies for OpenDevin.
@@ -199,52 +98,18 @@ def get_od_sandbox_image(
199
98
if 'ghcr.io/opendevin/sandbox' in base_image :
200
99
return base_image
201
100
202
- new_image_name = _get_new_image_name (base_image , is_eventstream_runtime )
101
+ new_image_name = _get_new_image_name (base_image )
203
102
204
103
# Detect if the sandbox image is built
205
- image_exists = False
206
104
images = docker_client .images .list ()
207
105
for image in images :
208
106
if new_image_name in image .tags :
209
107
logger .info ('Found existing od_sandbox image, reuse:' + new_image_name )
210
- image_exists = True
211
- break
212
-
213
- skip_init = False
214
- if image_exists :
215
- if is_eventstream_runtime :
216
- # An eventstream runtime image is already built for the base image (with poetry and dev dependencies)
217
- # but it might not contain the latest version of the source code and dependencies.
218
- # So we need to build a new (dev) image with the latest source code and dependencies.
219
- # FIXME: In production, we should just build once (since the source code will not change)
220
- base_image = new_image_name
221
- new_image_name = _get_new_image_name (
222
- base_image , is_eventstream_runtime , dev_mode = True
223
- )
224
-
225
- # Delete the existing image named `new_image_name` if any
226
- images = docker_client .images .list ()
227
- for image in images :
228
- if new_image_name in image .tags :
229
- docker_client .images .remove (image .id , force = True )
230
-
231
- # We will reuse the existing image but will update the source code in it.
232
- skip_init = True
233
- logger .info (
234
- f'Reusing existing od_sandbox image [{ base_image } ] but will update the source code into [{ new_image_name } ]'
235
- )
236
- else :
237
108
return new_image_name
238
- else :
239
- # If the sandbox image is not found, build it
240
- logger .info (
241
- f'od_sandbox image is not found for { base_image } , will build: { new_image_name } '
242
- )
243
- _build_sandbox_image (
244
- base_image ,
245
- new_image_name ,
246
- docker_client ,
247
- is_eventstream_runtime ,
248
- skip_init = skip_init ,
109
+
110
+ # If the sandbox image is not found, build it
111
+ logger .info (
112
+ f'od_sandbox image is not found for { base_image } , will build: { new_image_name } '
249
113
)
114
+ _build_sandbox_image (base_image , new_image_name , docker_client )
250
115
return new_image_name
0 commit comments