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.
@@ -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