ruby_llm-docker 0.0.1 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +255 -11
- data/Rakefile +3 -3
- data/examples/docker_chat.rb +228 -0
- data/examples/list_containers.rb +36 -0
- data/examples/test_chat.rb +81 -0
- data/lib/ruby_llm/docker/build_image.rb +111 -0
- data/lib/ruby_llm/docker/copy_to_container.rb +196 -0
- data/lib/ruby_llm/docker/create_container.rb +146 -0
- data/lib/ruby_llm/docker/create_network.rb +131 -0
- data/lib/ruby_llm/docker/create_volume.rb +129 -0
- data/lib/ruby_llm/docker/exec_container.rb +185 -0
- data/lib/ruby_llm/docker/fetch_container_logs.rb +132 -0
- data/lib/ruby_llm/docker/list_containers.rb +59 -0
- data/lib/ruby_llm/docker/list_images.rb +79 -0
- data/lib/ruby_llm/docker/list_networks.rb +81 -0
- data/lib/ruby_llm/docker/list_volumes.rb +88 -0
- data/lib/ruby_llm/docker/pull_image.rb +127 -0
- data/lib/ruby_llm/docker/push_image.rb +153 -0
- data/lib/ruby_llm/docker/recreate_container.rb +151 -0
- data/lib/ruby_llm/docker/remove_container.rb +120 -0
- data/lib/ruby_llm/docker/remove_image.rb +142 -0
- data/lib/ruby_llm/docker/remove_network.rb +120 -0
- data/lib/ruby_llm/docker/remove_volume.rb +127 -0
- data/lib/ruby_llm/docker/run_container.rb +104 -0
- data/lib/ruby_llm/docker/start_container.rb +97 -0
- data/lib/ruby_llm/docker/stop_container.rb +109 -0
- data/lib/ruby_llm/docker/tag_image.rb +139 -0
- data/lib/ruby_llm/docker/version.rb +2 -2
- data/lib/ruby_llm/docker.rb +53 -3
- data/sig/ruby_llm/docker.rbs +1 -1
- metadata +90 -5
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Docker
|
|
5
|
+
# RubyLLM tool for building Docker images from Dockerfile content.
|
|
6
|
+
#
|
|
7
|
+
# This tool provides the ability to build Docker images by providing Dockerfile
|
|
8
|
+
# content as a string. It supports optional tagging of the resulting image for
|
|
9
|
+
# easy identification and reuse.
|
|
10
|
+
#
|
|
11
|
+
# == Security Considerations
|
|
12
|
+
#
|
|
13
|
+
# Building Docker images can be potentially dangerous:
|
|
14
|
+
# - Dockerfile commands execute with Docker daemon privileges
|
|
15
|
+
# - Images can contain malicious software or backdoors
|
|
16
|
+
# - Build process can access host resources (network, files)
|
|
17
|
+
# - Base images may contain vulnerabilities
|
|
18
|
+
# - Build context may expose sensitive information
|
|
19
|
+
#
|
|
20
|
+
# Security recommendations:
|
|
21
|
+
# - Review Dockerfile content carefully before building
|
|
22
|
+
# - Use trusted base images from official repositories
|
|
23
|
+
# - Scan built images for vulnerabilities
|
|
24
|
+
# - Limit network access during builds
|
|
25
|
+
# - Avoid including secrets in Dockerfile instructions
|
|
26
|
+
# - Use multi-stage builds to minimize final image size
|
|
27
|
+
#
|
|
28
|
+
# == Features
|
|
29
|
+
#
|
|
30
|
+
# - Build images from Dockerfile content strings
|
|
31
|
+
# - Optional image tagging during build
|
|
32
|
+
# - Comprehensive error handling
|
|
33
|
+
# - Support for all standard Dockerfile instructions
|
|
34
|
+
# - Returns image ID and build status
|
|
35
|
+
#
|
|
36
|
+
# == Example Usage
|
|
37
|
+
#
|
|
38
|
+
# # Simple image build
|
|
39
|
+
# BuildImage.call(
|
|
40
|
+
# server_context: context,
|
|
41
|
+
# dockerfile: "FROM alpine:latest\nRUN apk add --no-cache curl"
|
|
42
|
+
# )
|
|
43
|
+
#
|
|
44
|
+
# # Build with custom tag
|
|
45
|
+
# BuildImage.call(
|
|
46
|
+
# server_context: context,
|
|
47
|
+
# dockerfile: dockerfile_content,
|
|
48
|
+
# tag: "myapp:v1.0"
|
|
49
|
+
# )
|
|
50
|
+
#
|
|
51
|
+
# @see Docker::Image.build
|
|
52
|
+
# @see TagImage
|
|
53
|
+
# @since 0.1.0
|
|
54
|
+
class BuildImage < RubyLLM::Tool
|
|
55
|
+
description 'Build a Docker image'
|
|
56
|
+
|
|
57
|
+
param :dockerfile, type: :string, desc: 'Dockerfile content as a string'
|
|
58
|
+
param :tag, type: :string, desc: 'Tag for the built image (e.g., "myimage:latest")', required: false
|
|
59
|
+
|
|
60
|
+
# Build a Docker image from Dockerfile content.
|
|
61
|
+
#
|
|
62
|
+
# This method creates a Docker image by building from the provided Dockerfile
|
|
63
|
+
# content string. The Dockerfile is processed by the Docker daemon and can
|
|
64
|
+
# include any valid Dockerfile instructions. Optionally, the resulting image
|
|
65
|
+
# can be tagged with a custom name for easy reference.
|
|
66
|
+
#
|
|
67
|
+
# @param dockerfile [String] the complete Dockerfile content as a string
|
|
68
|
+
# @param server_context [Object] RubyLLM context (unused but required)
|
|
69
|
+
# @param tag [String, nil] optional tag to apply to the built image
|
|
70
|
+
#
|
|
71
|
+
# @return [RubyLLM::Tool::Response] build results including image ID and tag info
|
|
72
|
+
#
|
|
73
|
+
# @raise [Docker::Error] for Docker daemon communication errors
|
|
74
|
+
# @raise [StandardError] for build failures or other errors
|
|
75
|
+
#
|
|
76
|
+
# @example Build simple image
|
|
77
|
+
# dockerfile = <<~DOCKERFILE
|
|
78
|
+
# FROM alpine:latest
|
|
79
|
+
# RUN apk add --no-cache nginx
|
|
80
|
+
# EXPOSE 80
|
|
81
|
+
# CMD ["nginx", "-g", "daemon off;"]
|
|
82
|
+
# DOCKERFILE
|
|
83
|
+
#
|
|
84
|
+
# response = tool.execute(
|
|
85
|
+
# dockerfile: dockerfile,
|
|
86
|
+
# tag: "my-nginx:latest"
|
|
87
|
+
# )
|
|
88
|
+
#
|
|
89
|
+
# @see Docker::Image.build
|
|
90
|
+
def execute(dockerfile:, tag: nil)
|
|
91
|
+
# Build the image
|
|
92
|
+
image = ::Docker::Image.build(dockerfile)
|
|
93
|
+
|
|
94
|
+
# If a tag was specified, tag the image
|
|
95
|
+
if tag
|
|
96
|
+
# Split tag into repo and tag parts
|
|
97
|
+
repo, image_tag = tag.split(':', 2)
|
|
98
|
+
image_tag ||= 'latest'
|
|
99
|
+
image.tag('repo' => repo, 'tag' => image_tag, 'force' => true)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
response_text = "Image built successfully. ID: #{image.id}"
|
|
103
|
+
response_text += ", Tag: #{tag}" if tag
|
|
104
|
+
|
|
105
|
+
response_text
|
|
106
|
+
rescue StandardError => e
|
|
107
|
+
"Error building image: #{e.message}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Docker
|
|
5
|
+
# RubyLLM tool for copying files and directories from host to Docker containers.
|
|
6
|
+
#
|
|
7
|
+
# This tool provides the ability to copy files or entire directory trees from
|
|
8
|
+
# the host filesystem into running Docker containers. It uses Docker's archive
|
|
9
|
+
# streaming API to efficiently transfer files while preserving permissions and
|
|
10
|
+
# directory structure.
|
|
11
|
+
#
|
|
12
|
+
# == ⚠️ SECURITY WARNING ⚠️
|
|
13
|
+
#
|
|
14
|
+
# This tool can be dangerous as it allows:
|
|
15
|
+
# - Reading arbitrary files from the host filesystem
|
|
16
|
+
# - Writing files into container filesystems
|
|
17
|
+
# - Potentially overwriting critical container files
|
|
18
|
+
# - Escalating privileges if used with setuid/setgid files
|
|
19
|
+
# - Exposing sensitive host data to containers
|
|
20
|
+
#
|
|
21
|
+
# Security recommendations:
|
|
22
|
+
# - Validate source paths to prevent directory traversal
|
|
23
|
+
# - Ensure containers run with minimal privileges
|
|
24
|
+
# - Monitor file copy operations for sensitive paths
|
|
25
|
+
# - Use read-only filesystems where possible
|
|
26
|
+
# - Implement proper access controls on source files
|
|
27
|
+
#
|
|
28
|
+
# == Features
|
|
29
|
+
#
|
|
30
|
+
# - Copy individual files or entire directories
|
|
31
|
+
# - Preserve file permissions and directory structure
|
|
32
|
+
# - Optional ownership changes after copy
|
|
33
|
+
# - Comprehensive error handling
|
|
34
|
+
# - Support for both absolute and relative paths
|
|
35
|
+
#
|
|
36
|
+
# == Example Usage
|
|
37
|
+
#
|
|
38
|
+
# # Copy a configuration file
|
|
39
|
+
# CopyToContainer.call(
|
|
40
|
+
# server_context: context,
|
|
41
|
+
# id: "web-server",
|
|
42
|
+
# source_path: "/host/config/nginx.conf",
|
|
43
|
+
# destination_path: "/etc/nginx/"
|
|
44
|
+
# )
|
|
45
|
+
#
|
|
46
|
+
# # Copy directory with ownership change
|
|
47
|
+
# CopyToContainer.call(
|
|
48
|
+
# server_context: context,
|
|
49
|
+
# id: "app-container",
|
|
50
|
+
# source_path: "/host/app/src",
|
|
51
|
+
# destination_path: "/app/",
|
|
52
|
+
# owner: "appuser:appgroup"
|
|
53
|
+
# )
|
|
54
|
+
#
|
|
55
|
+
# @see Docker::Container#archive_in_stream
|
|
56
|
+
# @since 0.1.0
|
|
57
|
+
class CopyToContainer < RubyLLM::Tool
|
|
58
|
+
description 'Copy a file or directory from the local filesystem into a running Docker container. ' \
|
|
59
|
+
'The source path is on the local machine, and the destination path is inside the container.'
|
|
60
|
+
|
|
61
|
+
param :id, type: :string, desc: 'Container ID or name'
|
|
62
|
+
param :source_path, type: :string, desc: 'Path to the file or directory on the local filesystem to copy'
|
|
63
|
+
param :destination_path, type: :string,
|
|
64
|
+
desc: 'Path inside the container where the file/directory should be copied'
|
|
65
|
+
param :owner, type: :string,
|
|
66
|
+
desc: 'Owner for the copied files (optional, e.g., "1000:1000" or "username:group")',
|
|
67
|
+
required: false
|
|
68
|
+
|
|
69
|
+
# Copy files or directories from host filesystem to a Docker container.
|
|
70
|
+
#
|
|
71
|
+
# This method creates a tar archive of the source path and streams it into
|
|
72
|
+
# the specified container using Docker's archive API. The operation preserves
|
|
73
|
+
# file permissions and directory structure. Optionally, ownership can be
|
|
74
|
+
# changed after the copy operation completes.
|
|
75
|
+
#
|
|
76
|
+
# The source path must exist on the host filesystem and be readable by the
|
|
77
|
+
# process running the application. The destination path must be a valid path
|
|
78
|
+
# within the container.
|
|
79
|
+
#
|
|
80
|
+
# @param id [String] container ID or name to copy files into
|
|
81
|
+
# @param source_path [String] path to file/directory on host filesystem
|
|
82
|
+
# @param destination_path [String] destination path inside container
|
|
83
|
+
# @param server_context [Object] RubyLLM context (unused but required)
|
|
84
|
+
# @param owner [String, nil] ownership specification (e.g., "user:group", "1000:1000")
|
|
85
|
+
#
|
|
86
|
+
# @return [RubyLLM::Tool::Response] success/failure message with operation details
|
|
87
|
+
#
|
|
88
|
+
# @raise [Docker::Error::NotFoundError] if container doesn't exist
|
|
89
|
+
# @raise [StandardError] for file system or Docker API errors
|
|
90
|
+
#
|
|
91
|
+
# @example Copy configuration file
|
|
92
|
+
# response = CopyToContainer.call(
|
|
93
|
+
# server_context: context,
|
|
94
|
+
# id: "nginx-container",
|
|
95
|
+
# source_path: "/etc/nginx/sites-available/default",
|
|
96
|
+
# destination_path: "/etc/nginx/sites-enabled/"
|
|
97
|
+
# )
|
|
98
|
+
#
|
|
99
|
+
# @example Copy directory with ownership
|
|
100
|
+
# response = tool.execute(
|
|
101
|
+
# id: "app-container",
|
|
102
|
+
# source_path: "/local/project",
|
|
103
|
+
# destination_path: "/app/",
|
|
104
|
+
# owner: "www-data:www-data"
|
|
105
|
+
# )
|
|
106
|
+
#
|
|
107
|
+
# @see Docker::Container#archive_in_stream
|
|
108
|
+
# @see #add_to_tar
|
|
109
|
+
def execute(id:, source_path:, destination_path:, owner: nil)
|
|
110
|
+
container = ::Docker::Container.get(id)
|
|
111
|
+
|
|
112
|
+
# Verify source path exists
|
|
113
|
+
return "Source path not found: #{source_path}" unless File.exist?(source_path)
|
|
114
|
+
|
|
115
|
+
# Create a tar archive of the source
|
|
116
|
+
tar_io = StringIO.new
|
|
117
|
+
tar_io.set_encoding('ASCII-8BIT')
|
|
118
|
+
|
|
119
|
+
Gem::Package::TarWriter.new(tar_io) do |tar|
|
|
120
|
+
self.class.add_to_tar(tar, source_path, File.basename(source_path))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
tar_io.rewind
|
|
124
|
+
|
|
125
|
+
# Copy to container
|
|
126
|
+
container.archive_in_stream(destination_path) do
|
|
127
|
+
tar_io.read
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Optionally change ownership
|
|
131
|
+
if owner
|
|
132
|
+
chown_path = File.join(destination_path, File.basename(source_path))
|
|
133
|
+
container.exec(['chown', '-R', owner, chown_path])
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
file_type = File.directory?(source_path) ? 'directory' : 'file'
|
|
137
|
+
response_text = "Successfully copied #{file_type} from #{source_path} to #{id}:#{destination_path}"
|
|
138
|
+
response_text += "\nOwnership changed to #{owner}" if owner
|
|
139
|
+
|
|
140
|
+
response_text
|
|
141
|
+
rescue ::Docker::Error::NotFoundError
|
|
142
|
+
"Container #{id} not found"
|
|
143
|
+
rescue StandardError => e
|
|
144
|
+
"Error copying to container: #{e.message}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Recursively add files and directories to a tar archive.
|
|
148
|
+
#
|
|
149
|
+
# This helper method builds a tar archive by recursively traversing
|
|
150
|
+
# the filesystem starting from the given path. It preserves file
|
|
151
|
+
# permissions and handles both files and directories appropriately.
|
|
152
|
+
#
|
|
153
|
+
# For directories, it creates directory entries in the tar and then
|
|
154
|
+
# recursively processes all contained files and subdirectories.
|
|
155
|
+
# For files, it reads the content and adds it to the tar with
|
|
156
|
+
# preserved permissions.
|
|
157
|
+
#
|
|
158
|
+
# @param tar [Gem::Package::TarWriter] the tar writer instance
|
|
159
|
+
# @param path [String] the filesystem path to add to the archive
|
|
160
|
+
# @param archive_path [String] the path within the tar archive
|
|
161
|
+
#
|
|
162
|
+
# @return [void]
|
|
163
|
+
#
|
|
164
|
+
# @example Add single file
|
|
165
|
+
# add_to_tar(tar_writer, "/host/file.txt", "file.txt")
|
|
166
|
+
#
|
|
167
|
+
# @example Add directory tree
|
|
168
|
+
# add_to_tar(tar_writer, "/host/mydir", "mydir")
|
|
169
|
+
#
|
|
170
|
+
# @see Gem::Package::TarWriter#mkdir
|
|
171
|
+
# @see Gem::Package::TarWriter#add_file_simple
|
|
172
|
+
def self.add_to_tar(tar, path, archive_path)
|
|
173
|
+
if File.directory?(path)
|
|
174
|
+
# Add directory entry
|
|
175
|
+
tar.mkdir(archive_path, File.stat(path).mode)
|
|
176
|
+
|
|
177
|
+
# Add directory contents
|
|
178
|
+
Dir.entries(path).each do |entry|
|
|
179
|
+
next if ['.', '..'].include?(entry)
|
|
180
|
+
|
|
181
|
+
full_path = File.join(path, entry)
|
|
182
|
+
archive_entry_path = File.join(archive_path, entry)
|
|
183
|
+
add_to_tar(tar, full_path, archive_entry_path)
|
|
184
|
+
end
|
|
185
|
+
else
|
|
186
|
+
# Add file
|
|
187
|
+
File.open(path, 'rb') do |file|
|
|
188
|
+
tar.add_file_simple(archive_path, File.stat(path).mode, file.size) do |tar_file|
|
|
189
|
+
IO.copy_stream(file, tar_file)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Docker
|
|
5
|
+
# RubyLLM tool for creating Docker containers without starting them.
|
|
6
|
+
#
|
|
7
|
+
# This tool provides the ability to create Docker containers from images with
|
|
8
|
+
# comprehensive configuration options. Unlike RunContainer, this tool only
|
|
9
|
+
# creates the container but does not start it, allowing for additional
|
|
10
|
+
# configuration or manual startup control.
|
|
11
|
+
#
|
|
12
|
+
# == Features
|
|
13
|
+
#
|
|
14
|
+
# - Create containers from any available Docker image
|
|
15
|
+
# - Full container configuration support
|
|
16
|
+
# - Port exposure and binding configuration
|
|
17
|
+
# - Volume mounting capabilities
|
|
18
|
+
# - Environment variable configuration
|
|
19
|
+
# - Custom command specification
|
|
20
|
+
# - Comprehensive error handling with specific error types
|
|
21
|
+
#
|
|
22
|
+
# == Security Considerations
|
|
23
|
+
#
|
|
24
|
+
# - Containers inherit Docker daemon security context
|
|
25
|
+
# - Host configuration can expose host resources
|
|
26
|
+
# - Volume mounts provide filesystem access
|
|
27
|
+
# - Port bindings expose services to networks
|
|
28
|
+
# - Environment variables may contain sensitive data
|
|
29
|
+
#
|
|
30
|
+
# Use appropriate security measures:
|
|
31
|
+
# - Validate image sources and integrity
|
|
32
|
+
# - Limit host resource exposure
|
|
33
|
+
# - Use read-only volumes where appropriate
|
|
34
|
+
# - Restrict network access through host configuration
|
|
35
|
+
# - Sanitize environment variables
|
|
36
|
+
#
|
|
37
|
+
# == Example Usage
|
|
38
|
+
#
|
|
39
|
+
# # Simple container creation
|
|
40
|
+
# CreateContainer.call(
|
|
41
|
+
# server_context: context,
|
|
42
|
+
# image: "nginx:latest",
|
|
43
|
+
# name: "web-server"
|
|
44
|
+
# )
|
|
45
|
+
#
|
|
46
|
+
# # Advanced container with configuration
|
|
47
|
+
# CreateContainer.call(
|
|
48
|
+
# server_context: context,
|
|
49
|
+
# image: "postgres:13",
|
|
50
|
+
# name: "database",
|
|
51
|
+
# env: "POSTGRES_PASSWORD=secret,POSTGRES_DB=myapp",
|
|
52
|
+
# exposed_ports: {"5432/tcp" => {}},
|
|
53
|
+
# host_config: {
|
|
54
|
+
# "PortBindings" => {"5432/tcp" => [{"HostPort" => "5432"}]},
|
|
55
|
+
# "Binds" => ["/host/data:/var/lib/postgresql/data:rw"]
|
|
56
|
+
# }
|
|
57
|
+
# )
|
|
58
|
+
#
|
|
59
|
+
# @see RunContainer
|
|
60
|
+
# @see StartContainer
|
|
61
|
+
# @see Docker::Container.create
|
|
62
|
+
# @since 0.1.0
|
|
63
|
+
class CreateContainer < RubyLLM::Tool
|
|
64
|
+
description 'Create a Docker container'
|
|
65
|
+
|
|
66
|
+
param :image, desc: 'Image name to use (e.g., "ubuntu:22.04")'
|
|
67
|
+
param :name, desc: 'Container name (optional)', required: false
|
|
68
|
+
param :cmd, desc: 'Command to run (optional)', required: false
|
|
69
|
+
param :env,
|
|
70
|
+
desc: 'Environment variables as comma-separated KEY=VALUE pairs ' \
|
|
71
|
+
'(optional, e.g., "VAR1=value1,VAR2=value2")',
|
|
72
|
+
required: false
|
|
73
|
+
param :exposed_ports, desc: 'Exposed ports as JSON object (optional)', required: false
|
|
74
|
+
param :host_config, desc: 'Host configuration including port bindings, volumes, etc. as JSON object (optional)',
|
|
75
|
+
required: false
|
|
76
|
+
|
|
77
|
+
# Create a new Docker container from an image.
|
|
78
|
+
#
|
|
79
|
+
# This method creates a container with the specified configuration but does
|
|
80
|
+
# not start it. The container can be started later using StartContainer or
|
|
81
|
+
# other Docker commands. All configuration parameters are optional except
|
|
82
|
+
# for the base image.
|
|
83
|
+
#
|
|
84
|
+
# @param image [String] Docker image name with optional tag (e.g., "nginx:latest")
|
|
85
|
+
# @param server_context [Object] RubyLLM context (unused but required)
|
|
86
|
+
# @param name [String, nil] custom name for the container
|
|
87
|
+
# @param cmd [String, nil] command to execute in the container
|
|
88
|
+
# @param env [String, nil] environment variables as comma-separated KEY=VALUE pairs
|
|
89
|
+
# @param exposed_ports [Hash, nil] ports to expose in {"port/protocol" => {}} format
|
|
90
|
+
# @param host_config [Hash, nil] Docker host configuration including bindings and volumes
|
|
91
|
+
#
|
|
92
|
+
# @return [RubyLLM::Tool::Response] creation results with container ID and name
|
|
93
|
+
#
|
|
94
|
+
# @raise [Docker::Error::NotFoundError] if the specified image doesn't exist
|
|
95
|
+
# @raise [Docker::Error::ConflictError] if container name already exists
|
|
96
|
+
# @raise [StandardError] for other creation failures
|
|
97
|
+
#
|
|
98
|
+
# @example Create simple container
|
|
99
|
+
# response = CreateContainer.call(
|
|
100
|
+
# server_context: context,
|
|
101
|
+
# image: "alpine:latest",
|
|
102
|
+
# name: "test-container"
|
|
103
|
+
# )
|
|
104
|
+
#
|
|
105
|
+
# @example Create container with full configuration
|
|
106
|
+
# response = CreateContainer.call(
|
|
107
|
+
# server_context: context,
|
|
108
|
+
# image: "redis:7-alpine",
|
|
109
|
+
# name: "redis-cache",
|
|
110
|
+
# env: "REDIS_PASSWORD=secret",
|
|
111
|
+
# exposed_ports: {"6379/tcp" => {}},
|
|
112
|
+
# host_config: {
|
|
113
|
+
# "PortBindings" => {"6379/tcp" => [{"HostPort" => "6379"}]},
|
|
114
|
+
# "RestartPolicy" => {"Name" => "unless-stopped"}
|
|
115
|
+
# }
|
|
116
|
+
# )
|
|
117
|
+
#
|
|
118
|
+
# @see Docker::Container.create
|
|
119
|
+
def execute(image:, name: nil, cmd: nil, env: nil, exposed_ports: nil, host_config: nil)
|
|
120
|
+
config = { 'Image' => image }
|
|
121
|
+
config['name'] = name if name
|
|
122
|
+
config['Cmd'] = cmd if cmd
|
|
123
|
+
|
|
124
|
+
# Parse environment variables string into array if provided
|
|
125
|
+
if env && !env.empty?
|
|
126
|
+
env_array = env.split(',').map(&:strip).select { |e| e.include?('=') }
|
|
127
|
+
config['Env'] = env_array unless env_array.empty?
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
config['ExposedPorts'] = exposed_ports if exposed_ports
|
|
131
|
+
config['HostConfig'] = host_config if host_config
|
|
132
|
+
|
|
133
|
+
container = ::Docker::Container.create(config)
|
|
134
|
+
container_name = container.info['Names']&.first&.delete_prefix('/')
|
|
135
|
+
|
|
136
|
+
"Container created successfully. ID: #{container.id}, Name: #{container_name}"
|
|
137
|
+
rescue ::Docker::Error::NotFoundError
|
|
138
|
+
"Image #{image} not found"
|
|
139
|
+
rescue ::Docker::Error::ConflictError
|
|
140
|
+
"Container with name #{name} already exists"
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
"Error creating container: #{e.message}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Docker
|
|
5
|
+
# RubyLLM tool for creating Docker networks.
|
|
6
|
+
#
|
|
7
|
+
# This tool provides the ability to create custom Docker networks for
|
|
8
|
+
# container communication and isolation. Networks enable containers to
|
|
9
|
+
# communicate securely while providing isolation from other network
|
|
10
|
+
# segments.
|
|
11
|
+
#
|
|
12
|
+
# == Features
|
|
13
|
+
#
|
|
14
|
+
# - Create custom Docker networks
|
|
15
|
+
# - Support for different network drivers
|
|
16
|
+
# - Duplicate name detection
|
|
17
|
+
# - Comprehensive error handling
|
|
18
|
+
# - Flexible network configuration
|
|
19
|
+
#
|
|
20
|
+
# == Network Drivers
|
|
21
|
+
#
|
|
22
|
+
# Docker supports various network drivers:
|
|
23
|
+
# - **bridge**: Default isolated network for single-host networking
|
|
24
|
+
# - **host**: Remove network isolation, use host networking directly
|
|
25
|
+
# - **overlay**: Multi-host networking for swarm services
|
|
26
|
+
# - **macvlan**: Assign MAC addresses to containers
|
|
27
|
+
# - **none**: Disable networking completely
|
|
28
|
+
# - **Custom**: Third-party network drivers
|
|
29
|
+
#
|
|
30
|
+
# == Security Considerations
|
|
31
|
+
#
|
|
32
|
+
# Network creation affects system security:
|
|
33
|
+
# - **Network Isolation**: Networks provide security boundaries
|
|
34
|
+
# - **Traffic Control**: Custom networks enable traffic filtering
|
|
35
|
+
# - **Access Control**: Networks control container communication
|
|
36
|
+
# - **DNS Resolution**: Networks provide internal DNS services
|
|
37
|
+
#
|
|
38
|
+
# Security implications:
|
|
39
|
+
# - Bridge networks isolate containers from host network
|
|
40
|
+
# - Host networks expose containers to all host traffic
|
|
41
|
+
# - Custom networks should follow least-privilege principles
|
|
42
|
+
# - Network names may reveal infrastructure details
|
|
43
|
+
#
|
|
44
|
+
# Best practices:
|
|
45
|
+
# - Use descriptive but not sensitive network names
|
|
46
|
+
# - Implement network segmentation strategies
|
|
47
|
+
# - Limit container access to necessary networks only
|
|
48
|
+
# - Monitor network traffic and connections
|
|
49
|
+
# - Regular audit of network configurations
|
|
50
|
+
#
|
|
51
|
+
# == Example Usage
|
|
52
|
+
#
|
|
53
|
+
# # Create basic bridge network
|
|
54
|
+
# CreateNetwork.call(
|
|
55
|
+
# server_context: context,
|
|
56
|
+
# name: "app-network"
|
|
57
|
+
# )
|
|
58
|
+
#
|
|
59
|
+
# # Create network with specific driver
|
|
60
|
+
# CreateNetwork.call(
|
|
61
|
+
# server_context: context,
|
|
62
|
+
# name: "frontend-net",
|
|
63
|
+
# driver: "bridge"
|
|
64
|
+
# )
|
|
65
|
+
#
|
|
66
|
+
# # Create without duplicate checking
|
|
67
|
+
# CreateNetwork.call(
|
|
68
|
+
# server_context: context,
|
|
69
|
+
# name: "temp-network",
|
|
70
|
+
# check_duplicate: false
|
|
71
|
+
# )
|
|
72
|
+
#
|
|
73
|
+
# @see ListNetworks
|
|
74
|
+
# @see RemoveNetwork
|
|
75
|
+
# @see Docker::Network.create
|
|
76
|
+
# @since 0.1.0
|
|
77
|
+
class CreateNetwork < RubyLLM::Tool
|
|
78
|
+
description 'Create a Docker network'
|
|
79
|
+
|
|
80
|
+
param :name, type: :string, desc: 'Name of the network'
|
|
81
|
+
param :driver, type: :string, desc: 'Driver to use (default: bridge)', required: false
|
|
82
|
+
param :check_duplicate, type: :boolean, desc: 'Check for networks with duplicate names (default: true)',
|
|
83
|
+
required: false
|
|
84
|
+
|
|
85
|
+
# Create a new Docker network.
|
|
86
|
+
#
|
|
87
|
+
# This method creates a custom Docker network with the specified name
|
|
88
|
+
# and driver. The network can then be used by containers for isolated
|
|
89
|
+
# communication.
|
|
90
|
+
#
|
|
91
|
+
# @param name [String] name for the new network
|
|
92
|
+
# @param server_context [Object] RubyLLM context (unused but required)
|
|
93
|
+
# @param driver [String] network driver to use (default: "bridge")
|
|
94
|
+
# @param check_duplicate [Boolean] whether to check for duplicate names (default: true)
|
|
95
|
+
#
|
|
96
|
+
# @return [RubyLLM::Tool::Response] network creation results with network ID
|
|
97
|
+
#
|
|
98
|
+
# @raise [Docker::Error::ConflictError] if network name already exists
|
|
99
|
+
# @raise [StandardError] for other network creation failures
|
|
100
|
+
#
|
|
101
|
+
# @example Create application network
|
|
102
|
+
# response = CreateNetwork.call(
|
|
103
|
+
# server_context: context,
|
|
104
|
+
# name: "webapp-network"
|
|
105
|
+
# )
|
|
106
|
+
#
|
|
107
|
+
# @example Create host network
|
|
108
|
+
# response = tool.execute(
|
|
109
|
+
# name: "high-performance-net",
|
|
110
|
+
# driver: "host"
|
|
111
|
+
# )
|
|
112
|
+
#
|
|
113
|
+
# @see Docker::Network.create
|
|
114
|
+
def execute(name:, driver: 'bridge', check_duplicate: true)
|
|
115
|
+
options = {
|
|
116
|
+
'Name' => name,
|
|
117
|
+
'Driver' => driver,
|
|
118
|
+
'CheckDuplicate' => check_duplicate
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
network = ::Docker::Network.create(name, options)
|
|
122
|
+
|
|
123
|
+
"Network #{name} created successfully. ID: #{network.id}"
|
|
124
|
+
rescue ::Docker::Error::ConflictError
|
|
125
|
+
"Network #{name} already exists"
|
|
126
|
+
rescue StandardError => e
|
|
127
|
+
"Error creating network: #{e.message}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|