Class: Daytona::Image
- Inherits:
-
Object
- Object
- Daytona::Image
- Defined in:
- lib/daytona/common/image.rb
Overview
Represents an image definition for a Daytona sandbox. Do not construct this class directly. Instead use one of its static factory methods, such as ‘Image.base()`, `Image.debian_slim()`, or `Image.from_dockerfile()`.
Constant Summary collapse
- SUPPORTED_PYTHON_SERIES =
Supported Python series
%w[3.9 3.10 3.11 3.12 3.13].freeze
- LATEST_PYTHON_MICRO_VERSIONS =
%w[3.9.22 3.10.17 3.11.12 3.12.10 3.13.3].freeze
Instance Attribute Summary collapse
-
#context_list ⇒ Array<Context>
readonly
List of context files for the image.
-
#dockerfile ⇒ String?
readonly
The generated Dockerfile for the image.
Class Method Summary collapse
-
.base(image) ⇒ Image
Creates an Image from an existing base image.
-
.debian_slim(python_version = nil) ⇒ Image
Creates a Debian slim image based on the official Python Docker image.
-
.from_dockerfile(path) ⇒ Image
Creates an Image from an existing Dockerfile.
Instance Method Summary collapse
-
#add_local_dir(local_path, remote_path) ⇒ Image
Adds a local directory to the image.
-
#add_local_file(local_path, remote_path) ⇒ Image
Adds a local file to the image.
-
#cmd(cmd) ⇒ Image
Sets the default command for the image.
-
#dockerfile_commands(dockerfile_commands, context_dir: nil) ⇒ Image
Adds arbitrary Dockerfile-like commands to the image.
-
#entrypoint(entrypoint_commands) ⇒ Image
Sets the entrypoint for the image.
-
#env(env_vars) ⇒ Image
Sets environment variables in the image.
-
#initialize(dockerfile: nil, context_list: []) ⇒ Image
constructor
A new instance of Image.
-
#pip_install(*packages, find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') ⇒ Image
Adds commands to install packages using pip.
-
#pip_install_from_pyproject(pyproject_toml, optional_dependencies: [], find_links: nil, index_url: nil, extra_index_url: nil, pre: false, extra_options: '') ⇒ Image
Installs dependencies from a pyproject.toml file.
-
#pip_install_from_requirements(requirements_txt, find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') ⇒ Image
Installs dependencies from a requirements.txt file.
-
#run_commands(*commands) ⇒ Image
Runs commands in the image.
-
#workdir(path) ⇒ Image
Sets the working directory in the image.
Constructor Details
#initialize(dockerfile: nil, context_list: []) ⇒ Image
Returns a new instance of Image.
37 38 39 40 |
# File 'lib/daytona/common/image.rb', line 37 def initialize(dockerfile: nil, context_list: []) @dockerfile = dockerfile || '' @context_list = context_list end |
Instance Attribute Details
#context_list ⇒ Array<Context> (readonly)
Returns List of context files for the image.
29 30 31 |
# File 'lib/daytona/common/image.rb', line 29 def context_list @context_list end |
#dockerfile ⇒ String? (readonly)
Returns The generated Dockerfile for the image.
26 27 28 |
# File 'lib/daytona/common/image.rb', line 26 def dockerfile @dockerfile end |
Class Method Details
.base(image) ⇒ Image
Creates an Image from an existing base image
309 310 311 312 313 |
# File 'lib/daytona/common/image.rb', line 309 def base(image) img = new img.instance_variable_set(:@dockerfile, "FROM #{image}\n") img end |
.debian_slim(python_version = nil) ⇒ Image
Creates a Debian slim image based on the official Python Docker image
322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/daytona/common/image.rb', line 322 def debian_slim(python_version = nil) # rubocop:disable Metrics/MethodLength python_version = process_python_version(python_version) img = new commands = [ "FROM python:#{python_version}-slim-bookworm", 'RUN apt-get update', 'RUN apt-get install -y gcc gfortran build-essential', 'RUN pip install --upgrade pip', # Set debian front-end to non-interactive to avoid users getting stuck with input prompts. "RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections" ] img.instance_variable_set(:@dockerfile, "#{commands.join("\n")}\n") img end |
.from_dockerfile(path) ⇒ Image
Creates an Image from an existing Dockerfile
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/daytona/common/image.rb', line 285 def from_dockerfile(path) # rubocop:disable Metrics/AbcSize path = Pathname.new(File.(path)) dockerfile = path.read img = new(dockerfile: dockerfile) # Remove dockerfile filename from path path_prefix = path.to_s.delete_suffix(path.basename.to_s) extract_copy_sources(dockerfile, path_prefix).each do |context_path, original_path| archive_base_path = context_path archive_base_path = context_path.delete_prefix(path_prefix) unless original_path.start_with?(path_prefix) img.context_list << Context.new(source_path: context_path, archive_path: archive_base_path) end img end |
Instance Method Details
#add_local_dir(local_path, remote_path) ⇒ Image
Adds a local directory to the image
154 155 156 157 158 159 160 161 |
# File 'lib/daytona/common/image.rb', line 154 def add_local_dir(local_path, remote_path) local_path = File.(local_path) archive_path = ObjectStorage.compute_archive_base_path(local_path) @context_list << Context.new(source_path: local_path, archive_path: archive_path) @dockerfile += "COPY #{archive_path} #{remote_path}\n" self end |
#add_local_file(local_path, remote_path) ⇒ Image
Adds a local file to the image
135 136 137 138 139 140 141 142 143 144 |
# File 'lib/daytona/common/image.rb', line 135 def add_local_file(local_path, remote_path) remote_path = "#{remote_path}/#{File.basename(local_path)}" if remote_path.end_with?('/') local_path = File.(local_path) archive_path = ObjectStorage.compute_archive_base_path(local_path) @context_list << Context.new(source_path: local_path, archive_path: archive_path) @dockerfile += "COPY #{archive_path} #{remote_path}\n" self end |
#cmd(cmd) ⇒ Image
Sets the default command for the image
239 240 241 242 243 244 245 246 247 248 |
# File 'lib/daytona/common/image.rb', line 239 def cmd(cmd) unless cmd.is_a?(Array) && cmd.all? { |x| x.is_a?(String) } raise Sdk::Error, 'Image CMD must be a list of strings.' end cmd_str = flatten_str_args('cmd', 'cmd', cmd) cmd_str = cmd_str.map { |arg| "\"#{arg}\"" }.join(', ') if cmd_str.any? @dockerfile += "CMD [#{cmd_str}]\n" self end |
#dockerfile_commands(dockerfile_commands, context_dir: nil) ⇒ Image
Adds arbitrary Dockerfile-like commands to the image
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/daytona/common/image.rb', line 258 def dockerfile_commands(dockerfile_commands, context_dir: nil) # rubocop:disable Metrics/MethodLength if context_dir context_dir = File.(context_dir) raise Sdk::Error, "Context directory #{context_dir} does not exist" unless Dir.exist?(context_dir) end # Extract copy sources from dockerfile commands extract_copy_sources(dockerfile_commands.join("\n"), context_dir || '').each do |context_path, original_path| archive_base_path = context_path if context_dir && !original_path.start_with?(context_dir) archive_base_path = context_path.delete_prefix(context_dir) end @context_list << Context.new(source_path: context_path, archive_path: archive_base_path) end @dockerfile += "#{dockerfile_commands.join("\n")}\n" self end |
#entrypoint(entrypoint_commands) ⇒ Image
Sets the entrypoint for the image
220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/daytona/common/image.rb', line 220 def entrypoint(entrypoint_commands) unless entrypoint_commands.is_a?(Array) && entrypoint_commands.all? { |x| x.is_a?(String) } raise Sdk::Error, 'entrypoint_commands must be a list of strings.' end args_str = flatten_str_args('entrypoint', 'entrypoint_commands', entrypoint_commands) args_str = args_str.map { |arg| "\"#{arg}\"" }.join(', ') if args_str.any? @dockerfile += "ENTRYPOINT [#{args_str}]\n" self end |
#env(env_vars) ⇒ Image
Sets environment variables in the image
190 191 192 193 194 195 196 197 198 199 |
# File 'lib/daytona/common/image.rb', line 190 def env(env_vars) non_str_keys = env_vars.reject { |_key, val| val.is_a?(String) }.keys raise Sdk::Error, "Image ENV variables must be strings. Invalid keys: #{non_str_keys}" unless non_str_keys.empty? env_vars.each do |key, val| @dockerfile += "ENV #{key}=#{Shellwords.escape(val)}\n" end self end |
#pip_install(*packages, find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') ⇒ Image
Adds commands to install packages using pip
54 55 56 57 58 59 60 61 62 |
# File 'lib/daytona/common/image.rb', line 54 def pip_install(*packages, find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') # rubocop:disable Metrics/ParameterLists pkgs = flatten_str_args('pip_install', 'packages', packages) return self if pkgs.empty? extra_args = format_pip_install_args(find_links:, index_url:, extra_index_urls:, pre:, extra_options:) @dockerfile += "RUN python -m pip install #{Shellwords.join(pkgs.sort)}#{extra_args}\n" self end |
#pip_install_from_pyproject(pyproject_toml, optional_dependencies: [], find_links: nil, index_url: nil, extra_index_url: nil, pre: false, extra_options: '') ⇒ Image
Installs dependencies from a pyproject.toml file
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/daytona/common/image.rb', line 106 def pip_install_from_pyproject(pyproject_toml, optional_dependencies: [], find_links: nil, index_url: nil, # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists extra_index_url: nil, pre: false, extra_options: '') data = TOML.load_file(pyproject_toml) dependencies = data.dig('project', 'dependencies') unless dependencies raise Sdk::Error, 'No [project.dependencies] section in pyproject.toml file. ' \ 'See https://packaging.python.org/en/latest/guides/writing-pyproject-toml ' \ 'for further file format guidelines.' end return unless optional_dependencies optionals = data.dig('project', 'optional-dependencies') optional_dependencies.each do |group| dependencies.concat(optionals.fetch(group, [])) end pip_install(*dependencies, find_links:, index_url:, extra_index_urls: extra_index_url, pre:, extra_options:) end |
#pip_install_from_requirements(requirements_txt, find_links: nil, index_url: nil, extra_index_urls: nil, pre: false, extra_options: '') ⇒ Image
Installs dependencies from a requirements.txt file
77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/daytona/common/image.rb', line 77 def pip_install_from_requirements(requirements_txt, find_links: nil, index_url: nil, extra_index_urls: nil, # rubocop:disable Metrics/ParameterLists pre: false, extra_options: '') requirements_txt = File.(requirements_txt) raise Sdk::Error, "Requirements file #{requirements_txt} does not exist" unless File.exist?(requirements_txt) extra_args = format_pip_install_args(find_links:, index_url:, extra_index_urls:, pre:, extra_options:) archive_path = ObjectStorage.compute_archive_base_path(requirements_txt) @context_list << Context.new(source_path: requirements_txt, archive_path:) @dockerfile += "COPY #{archive_path} /.requirements.txt\n" @dockerfile += "RUN python -m pip install -r /.requirements.txt#{extra_args}\n" self end |
#run_commands(*commands) ⇒ Image
Runs commands in the image
170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/daytona/common/image.rb', line 170 def run_commands(*commands) commands.each do |command| if command.is_a?(Array) escaped = command.map { |c| c.gsub('"', '\\"').gsub("'", "\\'") } @dockerfile += "RUN #{escaped.map { |c| "\"#{c}\"" }.join(' ')}\n" else @dockerfile += "RUN #{command}\n" end end self end |
#workdir(path) ⇒ Image
Sets the working directory in the image
208 209 210 211 |
# File 'lib/daytona/common/image.rb', line 208 def workdir(path) @dockerfile += "WORKDIR #{Shellwords.escape(path.to_s)}\n" self end |