ruby_llm-docker 0.2.5 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa62cc209279d8d55720d84a9f964ffac227e13f203f85b9819fabb8119d259c
4
- data.tar.gz: 47c3524da952c650163286685515f8eab9dbe21f8e03865c6c20ae5c5f8860dc
3
+ metadata.gz: 17035d50b89aaa2e3cfd6409b1a827a3dc9d6e8cc6013e703105b1bce1d6a80e
4
+ data.tar.gz: aac7610a23113ca2b1cad98e1d7eaa88210d069cf7cf9eaedf28736c526b2599
5
5
  SHA512:
6
- metadata.gz: aefbb193a31a8234556a20c03ec3cab0d51b85a85fd166f239780a6093d9cf37a30c05cead7de949562df00ed0fff31c98ff799f3f29cbb1e85970a2d7469a74
7
- data.tar.gz: 773dbc0e553ca52c969f60f0ec4ef5067dda65ab7c23a96fd4c49dc80649ec316adff48886b29fd589adbd93c3bada7afaa75192ad9cc0e0ebed055bd939451b
6
+ metadata.gz: 152b3442d3130660f7aad1815a7d16918fe20c61a06579f7ab4815a97af25c49cb581c39103b5b08bcf352ad0052cd8e9c601b9e320e2a0606ba3a7ee5b096fa
7
+ data.tar.gz: 041ce68408938affab9523541f1adb283a6c279e69d3b8abdcc6842c47154da1d0441dcb8a44a05f71c1ed382e4624c5d3136d45f5340c17ed8e9b8a96533176
@@ -13,21 +13,30 @@
13
13
  # export OPENAI_API_KEY='your-key-here'
14
14
  # ruby examples/docker_chat.rb
15
15
  #
16
+ # Debug mode:
17
+ # ruby examples/docker_chat.rb --debug # Enable debug output
18
+ # DOCKER_CHAT_DEBUG=true ruby examples/docker_chat.rb # Via environment variable
19
+ #
16
20
  # Commands:
17
21
  # /exit - Exit the chat
18
22
  # /help - Show available Docker tools
19
23
  # /tools - List all loaded tools
20
24
  # /clear - Clear the screen
25
+ # /debug - Toggle debug mode on/off
21
26
  # anything else - Send to OpenAI with Docker tools available
22
27
 
23
28
  require_relative '../lib/ruby_llm/docker'
24
29
  require 'io/console'
25
30
 
26
31
  # rubocop:disable Metrics/ClassLength
32
+
33
+ # Interactive Docker chat interface using RubyLLM and OpenAI.
34
+ # Provides natural language interaction with Docker containers, images, networks, and volumes.
27
35
  class DockerChat
28
36
  def initialize
29
37
  check_environment
30
38
  configure_ruby_llm
39
+ setup_debug_mode
31
40
  setup_chat
32
41
  @running = true
33
42
  end
@@ -54,8 +63,29 @@ class DockerChat
54
63
  end
55
64
  end
56
65
 
66
+ def setup_debug_mode
67
+ # Check for debug mode via environment variable or command line argument
68
+ @debug_mode = ENV['DOCKER_CHAT_DEBUG'] == 'true' || ARGV.include?('--debug') || ARGV.include?('-d')
69
+ debug_puts '🐛 Debug mode enabled' if @debug_mode
70
+ end
71
+
72
+ def debug_puts(message)
73
+ puts message if @debug_mode
74
+ end
75
+
57
76
  def setup_chat
77
+ # rubocop:disable Layout/BlockAlignment
78
+ # rubocop:disable Style/MultilineBlockChain
58
79
  @chat = RubyLLM.chat(model: 'gpt-4')
80
+ .on_tool_call do |tool_call|
81
+ debug_puts "🔧 Calling tool: #{tool_call.name}"
82
+ debug_puts "📝 Arguments: #{tool_call.arguments}"
83
+ end
84
+ .on_tool_result do |result|
85
+ debug_puts "✅ Tool returned: #{result}"
86
+ end
87
+ # rubocop:enable Layout/BlockAlignment
88
+ # rubocop:enable Style/MultilineBlockChain
59
89
 
60
90
  # Add all Docker tools to the chat
61
91
  RubyLLM::Docker.add_all_tools_to_chat(@chat)
@@ -78,9 +108,11 @@ class DockerChat
78
108
  puts ' /help - Show available Docker tools'
79
109
  puts ' /tools - List all loaded tools'
80
110
  puts ' /clear - Clear the screen'
111
+ puts ' /debug - Toggle debug mode on/off'
81
112
  puts
82
113
  puts '🚀 Ready! Type your questions or commands...'
83
114
  puts
115
+ debug_puts '🐛 Debug mode is currently enabled. Use /debug to toggle.'
84
116
  end
85
117
 
86
118
  def main_loop
@@ -118,6 +150,8 @@ class DockerChat
118
150
  show_tools
119
151
  when '/clear', '/c'
120
152
  clear_screen
153
+ when '/debug', '/d'
154
+ toggle_debug_mode
121
155
  when input.start_with?('/')
122
156
  puts "❓ Unknown command: #{input}"
123
157
  puts ' Type /help for available commands'
@@ -208,6 +242,13 @@ class DockerChat
208
242
  puts '🐳 Docker Chat - Screen cleared'
209
243
  end
210
244
 
245
+ def toggle_debug_mode
246
+ @debug_mode = !@debug_mode
247
+ status = @debug_mode ? 'enabled' : 'disabled'
248
+ puts "🐛 Debug mode #{status}"
249
+ debug_puts 'Debug output will now be shown for tool calls and results' if @debug_mode
250
+ end
251
+
211
252
  def show_goodbye
212
253
  puts "\n👋 Thanks for using Docker Chat!"
213
254
  puts ' Hope you found it helpful for managing your Docker environment.'
@@ -2,92 +2,90 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Docker
5
- # RubyLLM tool for building Docker images from Dockerfile content.
5
+ # MCP tool for building Docker images.
6
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.
7
+ # This tool provides the ability to build Docker images from Dockerfile
8
+ # content. It creates custom images by executing Dockerfile instructions
9
+ # and supports comprehensive build configuration including tagging and
10
+ # build arguments.
11
+ #
12
+ # == Features
13
+ #
14
+ # - Build images from Dockerfile content strings
15
+ # - Support for custom image tagging
16
+ # - Comprehensive build output and error reporting
17
+ # - Handles all standard Dockerfile instructions
18
+ # - Build context management
19
+ # - Progress tracking and logging
10
20
  #
11
21
  # == Security Considerations
12
22
  #
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
23
+ # Image building involves significant security risks:
24
+ # - **Code Execution**: Dockerfile RUN commands execute arbitrary code
25
+ # - **Network Access**: Build process can access networks and repositories
26
+ # - **File System Access**: Can read local files and directories
27
+ # - **Credential Exposure**: May expose build-time secrets and credentials
28
+ # - **Supply Chain Risk**: Downloaded packages may contain malware
29
+ # - **Resource Consumption**: Builds can consume significant CPU, memory, and storage
19
30
  #
20
- # Security recommendations:
21
- # - Review Dockerfile content carefully before building
22
- # - Use trusted base images from official repositories
31
+ # **Security Recommendations**:
32
+ # - Review all Dockerfile content before building
33
+ # - Use trusted base images only
34
+ # - Avoid embedding secrets in image layers
35
+ # - Implement build isolation and sandboxing
36
+ # - Monitor build resource consumption
23
37
  # - 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
38
+ # - Use multi-stage builds to minimize attack surface
27
39
  #
28
- # == Features
40
+ # == Parameters
29
41
  #
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
42
+ # - **dockerfile**: Dockerfile content as a string (required)
43
+ # - **tag**: Tag for the built image (optional, e.g., "myimage:latest")
35
44
  #
36
45
  # == Example Usage
37
46
  #
38
- # # Simple image build
39
- # BuildImage.call(
47
+ # # Build simple image
48
+ # dockerfile_content = <<~DOCKERFILE
49
+ # FROM alpine:latest
50
+ # RUN apk add --no-cache curl
51
+ # CMD ["curl", "--version"]
52
+ # DOCKERFILE
53
+ #
54
+ # response = BuildImage.call(
40
55
  # server_context: context,
41
- # dockerfile: "FROM alpine:latest\nRUN apk add --no-cache curl"
56
+ # dockerfile: dockerfile_content,
57
+ # tag: "my-curl:latest"
42
58
  # )
43
59
  #
44
- # # Build with custom tag
45
- # BuildImage.call(
60
+ # # Build web server image
61
+ # dockerfile_content = <<~DOCKERFILE
62
+ # FROM nginx:alpine
63
+ # COPY nginx.conf /etc/nginx/nginx.conf
64
+ # EXPOSE 80
65
+ # CMD ["nginx", "-g", "daemon off;"]
66
+ # DOCKERFILE
67
+ #
68
+ # response = BuildImage.call(
46
69
  # server_context: context,
47
70
  # dockerfile: dockerfile_content,
48
- # tag: "myapp:v1.0"
71
+ # tag: "custom-nginx:v1.0"
49
72
  # )
50
73
  #
51
- # @see Docker::Image.build
52
- # @see TagImage
74
+ # @see ::Docker::Image.build_from_dir
53
75
  # @since 0.1.0
54
- class BuildImage < RubyLLM::Tool
76
+ BUILD_IMAGE_DEFINITION = ToolForge.define(:build_image) do
55
77
  description 'Build a Docker image'
56
78
 
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
79
+ param :dockerfile,
80
+ type: :string,
81
+ description: 'Dockerfile content as a string'
59
82
 
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)
83
+ param :tag,
84
+ type: :string,
85
+ description: 'Tag for the built image (e.g., "myimage:latest")',
86
+ required: false
87
+
88
+ execute do |dockerfile:, tag: nil|
91
89
  # Build the image
92
90
  image = ::Docker::Image.build(dockerfile)
93
91
 
@@ -101,11 +99,12 @@ module RubyLLM
101
99
 
102
100
  response_text = "Image built successfully. ID: #{image.id}"
103
101
  response_text += ", Tag: #{tag}" if tag
104
-
105
102
  response_text
106
103
  rescue StandardError => e
107
104
  "Error building image: #{e.message}"
108
105
  end
109
106
  end
107
+
108
+ BuildImage = BUILD_IMAGE_DEFINITION.to_ruby_llm_tool
110
109
  end
111
110
  end
@@ -2,122 +2,124 @@
2
2
 
3
3
  module RubyLLM
4
4
  module Docker
5
- # RubyLLM tool for copying files and directories from host to Docker containers.
5
+ # MCP tool for copying files and directories to Docker containers.
6
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.
7
+ # This tool provides the ability to copy files and directories from the
8
+ # local host filesystem into running Docker containers. It supports both
9
+ # individual files and entire directory trees, with optional ownership
10
+ # modification within the container.
11
11
  #
12
- # == ⚠️ SECURITY WARNING ⚠️
12
+ # == Features
13
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
14
+ # - Copy files and directories from host to container
15
+ # - Supports recursive directory copying
16
+ # - Preserves file permissions and metadata
17
+ # - Optional ownership modification after copy
18
+ # - Works with running containers
19
+ # - Comprehensive error handling and validation
20
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
21
+ # == Security Considerations
27
22
  #
28
- # == Features
23
+ # File copying operations have significant security implications:
24
+ # - **File System Access**: Reads local host file system content
25
+ # - **Container Modification**: Alters container file system state
26
+ # - **Data Injection**: Can introduce malicious files into containers
27
+ # - **Permission Escalation**: May affect container security context
28
+ # - **Resource Consumption**: Large copies can consume storage and I/O
29
+ # - **Path Traversal**: Improper paths could access unintended locations
30
+ #
31
+ # **Security Recommendations**:
32
+ # - Validate and sanitize all file paths
33
+ # - Implement access controls for source file locations
34
+ # - Monitor file copy operations and sizes
35
+ # - Use read-only mounts where possible
36
+ # - Apply resource limits to prevent abuse
37
+ #
38
+ # == Parameters
29
39
  #
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
40
+ # - **id**: Container ID or name (required)
41
+ # - **source_path**: Path to file/directory on local filesystem (required)
42
+ # - **destination_path**: Path inside container for copied content (required)
43
+ # - **owner**: Owner for copied files (optional, e.g., "1000:1000" or "username:group")
35
44
  #
36
45
  # == Example Usage
37
46
  #
38
- # # Copy a configuration file
39
- # CopyToContainer.call(
47
+ # # Copy single file
48
+ # response = CopyToContainer.call(
40
49
  # server_context: context,
41
50
  # id: "web-server",
42
- # source_path: "/host/config/nginx.conf",
43
- # destination_path: "/etc/nginx/"
51
+ # source_path: "/local/config.conf",
52
+ # destination_path: "/etc/nginx/nginx.conf"
44
53
  # )
45
54
  #
46
55
  # # Copy directory with ownership change
47
- # CopyToContainer.call(
56
+ # response = CopyToContainer.call(
48
57
  # server_context: context,
49
58
  # id: "app-container",
50
- # source_path: "/host/app/src",
51
- # destination_path: "/app/",
52
- # owner: "appuser:appgroup"
59
+ # source_path: "/local/app-data",
60
+ # destination_path: "/var/lib/app",
61
+ # owner: "app:app"
53
62
  # )
54
63
  #
55
- # @see Docker::Container#archive_in_stream
64
+ # @see ::Docker::Container#archive_in_stream
56
65
  # @since 0.1.0
57
- class CopyToContainer < RubyLLM::Tool
66
+ COPY_TO_CONTAINER_DEFINITION = ToolForge.define(:copy_to_container) do
58
67
  description 'Copy a file or directory from the local filesystem into a running Docker container. ' \
59
68
  'The source path is on the local machine, and the destination path is inside the container.'
60
69
 
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)
70
+ param :id,
71
+ type: :string,
72
+ description: 'Container ID or name'
73
+
74
+ param :source_path,
75
+ type: :string,
76
+ description: 'Path to the file or directory on the local filesystem to copy'
77
+
78
+ param :destination_path,
79
+ type: :string,
80
+ description: 'Path inside the container where the file/directory should be copied'
81
+
82
+ param :owner,
83
+ type: :string,
84
+ description: 'Owner for the copied files (optional, e.g., "1000:1000" or "username:group")',
85
+ required: false
86
+
87
+ # Helper method for adding files/directories to tar
88
+ class_helper :add_to_tar do |tar, path, archive_path|
89
+ if File.directory?(path)
90
+ # Add directory entry
91
+ tar.mkdir(archive_path, File.stat(path).mode)
92
+
93
+ # Add directory contents
94
+ Dir.entries(path).each do |entry|
95
+ next if ['.', '..'].include?(entry)
96
+
97
+ full_path = File.join(path, entry)
98
+ archive_entry_path = File.join(archive_path, entry)
99
+ add_to_tar(tar, full_path, archive_entry_path)
100
+ end
101
+ else
102
+ # Add file
103
+ File.open(path, 'rb') do |file|
104
+ tar.add_file_simple(archive_path, File.stat(path).mode, file.size) do |tar_file|
105
+ IO.copy_stream(file, tar_file)
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ execute do |id:, source_path:, destination_path:, owner: nil|
110
112
  container = ::Docker::Container.get(id)
111
113
 
112
114
  # Verify source path exists
113
- return "Source path not found: #{source_path}" unless File.exist?(source_path)
115
+ next "Source path not found: #{source_path}" unless File.exist?(source_path)
114
116
 
115
117
  # Create a tar archive of the source
116
118
  tar_io = StringIO.new
117
119
  tar_io.set_encoding('ASCII-8BIT')
118
120
 
119
121
  Gem::Package::TarWriter.new(tar_io) do |tar|
120
- self.class.add_to_tar(tar, source_path, File.basename(source_path))
122
+ add_to_tar(tar, source_path, File.basename(source_path))
121
123
  end
122
124
 
123
125
  tar_io.rewind
@@ -136,61 +138,14 @@ module RubyLLM
136
138
  file_type = File.directory?(source_path) ? 'directory' : 'file'
137
139
  response_text = "Successfully copied #{file_type} from #{source_path} to #{id}:#{destination_path}"
138
140
  response_text += "\nOwnership changed to #{owner}" if owner
139
-
140
141
  response_text
141
142
  rescue ::Docker::Error::NotFoundError
142
143
  "Container #{id} not found"
143
144
  rescue StandardError => e
144
145
  "Error copying to container: #{e.message}"
145
146
  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
147
  end
148
+
149
+ CopyToContainer = COPY_TO_CONTAINER_DEFINITION.to_ruby_llm_tool
195
150
  end
196
151
  end