makit 0.0.168 → 0.0.169

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.
Files changed (179) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -41
  3. data/exe/makit +5 -5
  4. data/lib/makit/apache.rb +28 -28
  5. data/lib/makit/auto.rb +48 -48
  6. data/lib/makit/azure/blob_storage.rb +257 -257
  7. data/lib/makit/azure/cli.rb +284 -284
  8. data/lib/makit/azure-pipelines.rb +187 -187
  9. data/lib/makit/cli/base.rb +17 -17
  10. data/lib/makit/cli/build_commands.rb +500 -500
  11. data/lib/makit/cli/generators/base_generator.rb +74 -74
  12. data/lib/makit/cli/generators/dotnet_generator.rb +50 -50
  13. data/lib/makit/cli/generators/generator_factory.rb +49 -49
  14. data/lib/makit/cli/generators/node_generator.rb +50 -50
  15. data/lib/makit/cli/generators/ruby_generator.rb +77 -77
  16. data/lib/makit/cli/generators/rust_generator.rb +50 -50
  17. data/lib/makit/cli/generators/templates/dotnet_templates.rb +167 -167
  18. data/lib/makit/cli/generators/templates/node_templates.rb +161 -161
  19. data/lib/makit/cli/generators/templates/ruby/gemfile.rb +26 -26
  20. data/lib/makit/cli/generators/templates/ruby/gemspec.rb +41 -41
  21. data/lib/makit/cli/generators/templates/ruby/main_lib.rb +33 -33
  22. data/lib/makit/cli/generators/templates/ruby/rakefile.rb +35 -35
  23. data/lib/makit/cli/generators/templates/ruby/readme.rb +63 -63
  24. data/lib/makit/cli/generators/templates/ruby/test.rb +39 -39
  25. data/lib/makit/cli/generators/templates/ruby/test_helper.rb +29 -29
  26. data/lib/makit/cli/generators/templates/ruby/version.rb +29 -29
  27. data/lib/makit/cli/generators/templates/rust_templates.rb +128 -128
  28. data/lib/makit/cli/main.rb +78 -78
  29. data/lib/makit/cli/pipeline_commands.rb +311 -311
  30. data/lib/makit/cli/project_commands.rb +868 -868
  31. data/lib/makit/cli/repository_commands.rb +661 -661
  32. data/lib/makit/cli/strategy_commands.rb +207 -207
  33. data/lib/makit/cli/utility_commands.rb +521 -521
  34. data/lib/makit/commands/factory.rb +359 -359
  35. data/lib/makit/commands/middleware/base.rb +73 -73
  36. data/lib/makit/commands/middleware/cache.rb +248 -248
  37. data/lib/makit/commands/middleware/command_logger.rb +312 -312
  38. data/lib/makit/commands/middleware/validator.rb +269 -269
  39. data/lib/makit/commands/request.rb +316 -316
  40. data/lib/makit/commands/result.rb +323 -323
  41. data/lib/makit/commands/runner.rb +386 -386
  42. data/lib/makit/commands/strategies/base.rb +171 -171
  43. data/lib/makit/commands/strategies/child_process.rb +162 -162
  44. data/lib/makit/commands/strategies/factory.rb +136 -136
  45. data/lib/makit/commands/strategies/synchronous.rb +139 -139
  46. data/lib/makit/commands.rb +50 -50
  47. data/lib/makit/configuration/dotnet_project.rb +48 -48
  48. data/lib/makit/configuration/gitlab_helper.rb +61 -61
  49. data/lib/makit/configuration/project.rb +292 -292
  50. data/lib/makit/configuration/rakefile_helper.rb +43 -43
  51. data/lib/makit/configuration/step.rb +34 -34
  52. data/lib/makit/configuration/timeout.rb +74 -74
  53. data/lib/makit/configuration.rb +21 -21
  54. data/lib/makit/content/default_gitignore.rb +7 -7
  55. data/lib/makit/content/default_gitignore.txt +225 -225
  56. data/lib/makit/content/default_rakefile.rb +13 -13
  57. data/lib/makit/content/gem_rakefile.rb +16 -16
  58. data/lib/makit/context.rb +1 -1
  59. data/lib/makit/data.rb +49 -49
  60. data/lib/makit/directories.rb +170 -170
  61. data/lib/makit/directory.rb +262 -262
  62. data/lib/makit/docs/files.rb +89 -89
  63. data/lib/makit/docs/rake.rb +102 -102
  64. data/lib/makit/dotnet/cli.rb +224 -224
  65. data/lib/makit/dotnet/project.rb +217 -217
  66. data/lib/makit/dotnet/solution.rb +38 -38
  67. data/lib/makit/dotnet/solution_classlib.rb +239 -239
  68. data/lib/makit/dotnet/solution_console.rb +264 -264
  69. data/lib/makit/dotnet/solution_maui.rb +354 -354
  70. data/lib/makit/dotnet/solution_wasm.rb +275 -275
  71. data/lib/makit/dotnet/solution_wpf.rb +304 -304
  72. data/lib/makit/dotnet.rb +110 -110
  73. data/lib/makit/email.rb +90 -90
  74. data/lib/makit/environment.rb +142 -142
  75. data/lib/makit/examples/runner.rb +370 -370
  76. data/lib/makit/exceptions.rb +45 -45
  77. data/lib/makit/fileinfo.rb +32 -32
  78. data/lib/makit/files.rb +43 -43
  79. data/lib/makit/gems.rb +49 -49
  80. data/lib/makit/git/cli.rb +103 -103
  81. data/lib/makit/git/repository.rb +100 -100
  82. data/lib/makit/git.rb +104 -104
  83. data/lib/makit/github_actions.rb +202 -202
  84. data/lib/makit/gitlab/pipeline.rb +857 -857
  85. data/lib/makit/gitlab/pipeline_service_impl.rb +1535 -1535
  86. data/lib/makit/gitlab_runner.rb +59 -59
  87. data/lib/makit/humanize.rb +218 -218
  88. data/lib/makit/indexer.rb +47 -47
  89. data/lib/makit/io/filesystem.rb +111 -111
  90. data/lib/makit/io/filesystem_service_impl.rb +337 -337
  91. data/lib/makit/lint.rb +212 -212
  92. data/lib/makit/logging/configuration.rb +309 -309
  93. data/lib/makit/logging/format_registry.rb +84 -84
  94. data/lib/makit/logging/formatters/base.rb +39 -39
  95. data/lib/makit/logging/formatters/console_formatter.rb +140 -140
  96. data/lib/makit/logging/formatters/json_formatter.rb +65 -65
  97. data/lib/makit/logging/formatters/plain_text_formatter.rb +71 -71
  98. data/lib/makit/logging/formatters/text_formatter.rb +64 -64
  99. data/lib/makit/logging/log_request.rb +119 -119
  100. data/lib/makit/logging/logger.rb +199 -199
  101. data/lib/makit/logging/sinks/base.rb +91 -91
  102. data/lib/makit/logging/sinks/console.rb +72 -72
  103. data/lib/makit/logging/sinks/file_sink.rb +92 -92
  104. data/lib/makit/logging/sinks/structured.rb +123 -123
  105. data/lib/makit/logging/sinks/unified_file_sink.rb +296 -296
  106. data/lib/makit/logging.rb +578 -578
  107. data/lib/makit/markdown.rb +75 -75
  108. data/lib/makit/mp/basic_object_mp.rb +17 -17
  109. data/lib/makit/mp/command_mp.rb +13 -13
  110. data/lib/makit/mp/command_request.mp.rb +17 -17
  111. data/lib/makit/mp/project_mp.rb +199 -199
  112. data/lib/makit/mp/string_mp.rb +205 -205
  113. data/lib/makit/nuget.rb +460 -458
  114. data/lib/makit/podman/podman.rb +458 -458
  115. data/lib/makit/podman/podman_service_impl.rb +1081 -1081
  116. data/lib/makit/port.rb +32 -32
  117. data/lib/makit/process.rb +377 -377
  118. data/lib/makit/protoc.rb +112 -112
  119. data/lib/makit/rake/cli.rb +196 -196
  120. data/lib/makit/rake/trace_controller.rb +174 -174
  121. data/lib/makit/rake.rb +81 -81
  122. data/lib/makit/ruby/cli.rb +185 -185
  123. data/lib/makit/ruby.rb +25 -25
  124. data/lib/makit/rubygems.rb +137 -137
  125. data/lib/makit/secrets/azure_key_vault.rb +322 -322
  126. data/lib/makit/secrets/azure_secrets.rb +221 -221
  127. data/lib/makit/secrets/local_secrets.rb +72 -72
  128. data/lib/makit/secrets/secrets_manager.rb +105 -105
  129. data/lib/makit/secrets.rb +96 -96
  130. data/lib/makit/serializer.rb +130 -130
  131. data/lib/makit/services/builder.rb +186 -186
  132. data/lib/makit/services/error_handler.rb +226 -226
  133. data/lib/makit/services/repository_manager.rb +367 -367
  134. data/lib/makit/services/validator.rb +112 -112
  135. data/lib/makit/setup/classlib.rb +101 -101
  136. data/lib/makit/setup/gem.rb +268 -268
  137. data/lib/makit/setup/pages.rb +11 -11
  138. data/lib/makit/setup/razorclasslib.rb +101 -101
  139. data/lib/makit/setup/runner.rb +54 -54
  140. data/lib/makit/setup.rb +5 -5
  141. data/lib/makit/show.rb +110 -110
  142. data/lib/makit/storage.rb +126 -126
  143. data/lib/makit/symbols.rb +175 -175
  144. data/lib/makit/task_info.rb +130 -130
  145. data/lib/makit/tasks/at_exit.rb +15 -15
  146. data/lib/makit/tasks/build.rb +22 -22
  147. data/lib/makit/tasks/bump.rb +7 -7
  148. data/lib/makit/tasks/clean.rb +13 -13
  149. data/lib/makit/tasks/configure.rb +10 -10
  150. data/lib/makit/tasks/format.rb +10 -10
  151. data/lib/makit/tasks/hook_manager.rb +443 -443
  152. data/lib/makit/tasks/info.rb +368 -368
  153. data/lib/makit/tasks/init.rb +49 -49
  154. data/lib/makit/tasks/integrate.rb +60 -60
  155. data/lib/makit/tasks/pull_incoming.rb +13 -13
  156. data/lib/makit/tasks/secrets.rb +7 -7
  157. data/lib/makit/tasks/setup.rb +16 -16
  158. data/lib/makit/tasks/sync.rb +14 -14
  159. data/lib/makit/tasks/tag.rb +27 -27
  160. data/lib/makit/tasks/task_monkey_patch.rb +81 -81
  161. data/lib/makit/tasks/test.rb +22 -22
  162. data/lib/makit/tasks/update.rb +21 -21
  163. data/lib/makit/tasks/version.rb +6 -6
  164. data/lib/makit/tasks.rb +24 -24
  165. data/lib/makit/test_cache.rb +239 -239
  166. data/lib/makit/tree.rb +37 -37
  167. data/lib/makit/v1/configuration/project_service_impl.rb +370 -370
  168. data/lib/makit/v1/git/git_repository_service_impl.rb +295 -295
  169. data/lib/makit/v1/makit.v1_pb.rb +35 -35
  170. data/lib/makit/v1/makit.v1_services_pb.rb +27 -27
  171. data/lib/makit/v1/services/repository_manager_service_impl.rb +572 -572
  172. data/lib/makit/version.rb +661 -661
  173. data/lib/makit/version_util.rb +21 -21
  174. data/lib/makit/wix.rb +95 -95
  175. data/lib/makit/yaml.rb +29 -29
  176. data/lib/makit/zip.rb +17 -17
  177. data/lib/makit copy.rb +44 -44
  178. data/lib/makit.rb +121 -121
  179. metadata +2 -2
@@ -1,1081 +1,1081 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "time"
5
- require "securerandom"
6
-
7
- module Makit
8
- module Podman
9
- class PodmanServiceImpl
10
- def initialize(podman_executable: "podman")
11
- @podman_executable = find_executable(podman_executable)
12
- raise "Podman executable not found" unless @podman_executable
13
- end
14
-
15
- # Check if gRPC service is available
16
- def self.grpc_available?
17
- defined?(Makit::V1::Makit::V1::Podman::Service::PodmanService)
18
- end
19
-
20
- # Image operations
21
- def pull_image(request, _unused_call = nil)
22
- cmd = [@podman_executable, "pull"]
23
- cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
24
- cmd << "#{request.respond_to?(:image_name) ? request.image_name : request[:image_name]}:#{request.respond_to?(:tag) ? request.tag : request[:tag]}"
25
-
26
- result = execute_shell_command(cmd.join(" "))
27
-
28
- if result[:success]
29
- # Extract image ID from output
30
- image_id = extract_image_id(result[:stdout])
31
- if grpc_available?
32
- Makit::V1::Podman::Service::PullImageResponse.new(
33
- success: true,
34
- image_id: image_id,
35
- layers: extract_layers(result[:stdout]),
36
- pulled_at: Time.now
37
- )
38
- else
39
- {
40
- success: true,
41
- image_id: image_id,
42
- layers: extract_layers(result[:stdout]),
43
- pulled_at: Time.now
44
- }
45
- end
46
- else
47
- if grpc_available?
48
- Makit::V1::Podman::Service::PullImageResponse.new(
49
- success: false,
50
- error_message: result[:stderr]
51
- )
52
- else
53
- {
54
- success: false,
55
- error_message: result[:stderr]
56
- }
57
- end
58
- end
59
- end
60
-
61
- def list_images(request, _unused_call = nil)
62
- cmd = [@podman_executable, "images", "--format", "json"]
63
- cmd << "--all" if request.respond_to?(:all) ? request.all : request[:all]
64
-
65
- result = execute_shell_command(cmd.join(" "))
66
-
67
- if result[:success]
68
- images_data = JSON.parse(result[:stdout])
69
- images = images_data.map do |img|
70
- # Extract repository and tag from Names array
71
- names = img["Names"] || []
72
- repository = "unknown"
73
- tag = "latest"
74
-
75
- if names.any?
76
- name_parts = names.first.split(":")
77
- repository = name_parts[0]
78
- tag = name_parts[1] || "latest"
79
- end
80
-
81
- created_time = if img["Created"].is_a?(String)
82
- Time.parse(img["Created"])
83
- else
84
- Time.at(img["Created"])
85
- end
86
-
87
- if grpc_available?
88
- Makit::V1::Podman::Service::Image.new(
89
- id: img["Id"],
90
- repository: repository,
91
- tag: tag,
92
- digest: img["Digest"],
93
- created: created_time,
94
- size: img["Size"].to_i,
95
- labels: img["Labels"] || {}
96
- )
97
- else
98
- {
99
- id: img["Id"],
100
- repository: repository,
101
- tag: tag,
102
- digest: img["Digest"],
103
- created: created_time,
104
- size: img["Size"].to_i,
105
- labels: img["Labels"] || {}
106
- }
107
- end
108
- end
109
-
110
- if grpc_available?
111
- Makit::V1::Podman::Service::ListImagesResponse.new(
112
- images: images,
113
- total_count: images.length
114
- )
115
- else
116
- {
117
- images: images,
118
- total_count: images.length
119
- }
120
- end
121
- else
122
- if grpc_available?
123
- Makit::V1::Podman::Service::ListImagesResponse.new(
124
- images: [],
125
- total_count: 0
126
- )
127
- else
128
- {
129
- images: [],
130
- total_count: 0
131
- }
132
- end
133
- end
134
- end
135
-
136
- def remove_image(request, _unused_call = nil)
137
- cmd = [@podman_executable, "rmi"]
138
- cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
139
- cmd << (request.respond_to?(:image_id) ? request.image_id : request[:image_id])
140
-
141
- result = execute_shell_command(cmd.join(" "))
142
-
143
- if grpc_available?
144
- Makit::V1::Podman::Service::RemoveImageResponse.new(
145
- success: result[:success],
146
- error_message: result[:success] ? "" : result[:stderr]
147
- )
148
- else
149
- {
150
- success: result[:success],
151
- error_message: result[:success] ? "" : result[:stderr]
152
- }
153
- end
154
- end
155
-
156
- def inspect_image(request, _unused_call = nil)
157
- cmd = [@podman_executable, "inspect", request.respond_to?(:image_id) ? request.image_id : request[:image_id]]
158
- result = execute_shell_command(cmd.join(" "))
159
-
160
- if grpc_available?
161
- Makit::V1::Podman::Service::InspectImageResponse.new(
162
- success: result[:success],
163
- image_info: result[:success] ? result[:stdout] : "",
164
- error_message: result[:success] ? "" : result[:stderr]
165
- )
166
- else
167
- {
168
- success: result[:success],
169
- image_info: result[:success] ? result[:stdout] : "",
170
- error_message: result[:success] ? "" : result[:stderr]
171
- }
172
- end
173
- end
174
-
175
- # Container operations
176
- def create_container(request, _unused_call = nil)
177
- cmd = [@podman_executable, "create"]
178
- name = request.respond_to?(:name) ? request.name : request[:name]
179
- working_directory = request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]
180
- user = request.respond_to?(:user) ? request.user : request[:user]
181
- privileged = request.respond_to?(:privileged) ? request.privileged : request[:privileged]
182
- auto_remove = request.respond_to?(:auto_remove) ? request.auto_remove : request[:auto_remove]
183
- environment = request.respond_to?(:environment) ? request.environment : request[:environment]
184
- volume_mounts = request.respond_to?(:volume_mounts) ? request.volume_mounts : request[:volume_mounts]
185
- port_mappings = request.respond_to?(:port_mappings) ? request.port_mappings : request[:port_mappings]
186
- labels = request.respond_to?(:labels) ? request.labels : request[:labels]
187
- image = request.respond_to?(:image) ? request.image : request[:image]
188
- command = request.respond_to?(:command) ? request.command : request[:command]
189
- args = request.respond_to?(:args) ? request.args : request[:args]
190
-
191
- cmd << "--name" << name if name
192
- cmd << "--workdir" << working_directory if working_directory
193
- cmd << "--user" << user if user
194
- cmd << "--privileged" if privileged
195
- cmd << "--rm" if auto_remove
196
-
197
- # Add environment variables
198
- (environment || {}).each do |key, value|
199
- cmd << "--env" << "#{key}=#{value}"
200
- end
201
-
202
- # Add volume mounts
203
- (volume_mounts || []).each do |mount|
204
- host_path = mount.respond_to?(:host_path) ? mount.host_path : mount[:host_path]
205
- container_path = mount.respond_to?(:container_path) ? mount.container_path : mount[:container_path]
206
- mode = mount.respond_to?(:mode) ? mount.mode : mount[:mode]
207
-
208
- # Convert Windows paths to WSL paths on Windows
209
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
210
- host_path = convert_to_wsl_path(host_path)
211
- end
212
-
213
- cmd << "--volume" << "#{host_path}:#{container_path}:#{mode}"
214
- end
215
-
216
- # Add port mappings
217
- (port_mappings || []).each do |port|
218
- host_port = port.respond_to?(:host_port) ? port.host_port : port[:host_port]
219
- container_port = port.respond_to?(:container_port) ? port.container_port : port[:container_port]
220
- protocol = port.respond_to?(:protocol) ? port.protocol : port[:protocol]
221
- cmd << "--publish" << "#{host_port}:#{container_port}/#{protocol}"
222
- end
223
-
224
- # Add labels
225
- (labels || {}).each do |key, value|
226
- cmd << "--label" << "#{key}=#{value}"
227
- end
228
-
229
- cmd << image
230
-
231
- # Add command and args
232
- if command && command.any?
233
- cmd.concat(command)
234
- end
235
- if args && args.any?
236
- cmd.concat(args)
237
- end
238
-
239
- result = execute_shell_command(cmd.join(" "))
240
-
241
- if result[:success]
242
- container_id = result[:stdout].strip
243
- if grpc_available?
244
- Makit::V1::Podman::Service::CreateContainerResponse.new(
245
- success: true,
246
- container_id: container_id,
247
- container_name: request.respond_to?(:name) ? request.name : request[:name]
248
- )
249
- else
250
- {
251
- success: true,
252
- container_id: container_id,
253
- container_name: request.respond_to?(:name) ? request.name : request[:name]
254
- }
255
- end
256
- else
257
- if grpc_available?
258
- Makit::V1::Podman::Service::CreateContainerResponse.new(
259
- success: false,
260
- error_message: result[:stderr]
261
- )
262
- else
263
- {
264
- success: false,
265
- error_message: result[:stderr]
266
- }
267
- end
268
- end
269
- end
270
-
271
- def start_container(request, _unused_call = nil)
272
- cmd = [@podman_executable, "start", request.respond_to?(:container_id) ? request.container_id : request[:container_id]]
273
- result = execute_shell_command(cmd.join(" "))
274
-
275
- if grpc_available?
276
- Makit::V1::Podman::Service::StartContainerResponse.new(
277
- success: result[:success],
278
- error_message: result[:success] ? "" : result[:stderr]
279
- )
280
- else
281
- {
282
- success: result[:success],
283
- error_message: result[:success] ? "" : result[:stderr]
284
- }
285
- end
286
- end
287
-
288
- def stop_container(request, _unused_call = nil)
289
- cmd = [@podman_executable, "stop"]
290
- timeout_seconds = request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds]
291
- cmd << "--time" << timeout_seconds.to_s if timeout_seconds && timeout_seconds > 0
292
- cmd << (request.respond_to?(:container_id) ? request.container_id : request[:container_id])
293
-
294
- result = execute_shell_command(cmd.join(" "))
295
-
296
- if grpc_available?
297
- Makit::V1::Podman::Service::StopContainerResponse.new(
298
- success: result[:success],
299
- error_message: result[:success] ? "" : result[:stderr]
300
- )
301
- else
302
- {
303
- success: result[:success],
304
- error_message: result[:success] ? "" : result[:stderr]
305
- }
306
- end
307
- end
308
-
309
- def remove_container(request, _unused_call = nil)
310
- cmd = [@podman_executable, "rm"]
311
- cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
312
- cmd << (request.respond_to?(:container_id) ? request.container_id : request[:container_id])
313
-
314
- result = execute_shell_command(cmd.join(" "))
315
-
316
- if grpc_available?
317
- Makit::V1::Podman::Service::RemoveContainerResponse.new(
318
- success: result[:success],
319
- error_message: result[:success] ? "" : result[:stderr]
320
- )
321
- else
322
- {
323
- success: result[:success],
324
- error_message: result[:success] ? "" : result[:stderr]
325
- }
326
- end
327
- end
328
-
329
- def list_containers(request, _unused_call = nil)
330
- cmd = [@podman_executable, "ps", "--format", "json"]
331
- cmd << "--all" if request.respond_to?(:all) ? request.all : request[:all]
332
-
333
- result = execute_shell_command(cmd.join(" "))
334
-
335
- if result[:success]
336
- containers_data = JSON.parse(result[:stdout])
337
- containers = containers_data.map do |container|
338
- if grpc_available?
339
- Makit::V1::Podman::Service::Container.new(
340
- id: container["Id"],
341
- name: container["Names"][0],
342
- image: container["Image"],
343
- status: map_container_status(container["State"]),
344
- created: parse_time(container["CreatedAt"]),
345
- started: container["StartedAt"] ? parse_time(container["StartedAt"]) : nil,
346
- finished: container["FinishedAt"] ? parse_time(container["FinishedAt"]) : nil,
347
- command: container["Command"] ? [container["Command"]] : [],
348
- environment: container["Env"] || {},
349
- labels: container["Labels"] || {}
350
- )
351
- else
352
- {
353
- id: container["Id"],
354
- name: container["Names"][0],
355
- image: container["Image"],
356
- status: map_container_status(container["State"]),
357
- created: parse_time(container["CreatedAt"]),
358
- started: container["StartedAt"] ? parse_time(container["StartedAt"]) : nil,
359
- finished: container["FinishedAt"] ? parse_time(container["FinishedAt"]) : nil,
360
- command: container["Command"] ? [container["Command"]] : [],
361
- environment: container["Env"] || {},
362
- labels: container["Labels"] || {}
363
- }
364
- end
365
- end
366
-
367
- if grpc_available?
368
- Makit::V1::Podman::Service::ListContainersResponse.new(
369
- containers: containers,
370
- total_count: containers.length
371
- )
372
- else
373
- {
374
- containers: containers,
375
- total_count: containers.length
376
- }
377
- end
378
- else
379
- if grpc_available?
380
- Makit::V1::Podman::Service::ListContainersResponse.new(
381
- containers: [],
382
- total_count: 0
383
- )
384
- else
385
- {
386
- containers: [],
387
- total_count: 0
388
- }
389
- end
390
- end
391
- end
392
-
393
- def inspect_container(request, _unused_call = nil)
394
- cmd = [@podman_executable, "inspect", request.respond_to?(:container_id) ? request.container_id : request[:container_id]]
395
- result = execute_shell_command(cmd.join(" "))
396
-
397
- if grpc_available?
398
- Makit::V1::Podman::Service::InspectContainerResponse.new(
399
- success: result[:success],
400
- container_info: result[:success] ? result[:stdout] : "",
401
- error_message: result[:success] ? "" : result[:stderr]
402
- )
403
- else
404
- {
405
- success: result[:success],
406
- container_info: result[:success] ? result[:stdout] : "",
407
- error_message: result[:success] ? "" : result[:stderr]
408
- }
409
- end
410
- end
411
-
412
- # Execution operations
413
- def execute_script(request, _unused_call = nil)
414
- # Create temporary script file
415
- script_file = "/tmp/#{SecureRandom.uuid}.sh"
416
-
417
- begin
418
- # Ensure /tmp directory exists
419
- mkdir_cmd = [@podman_executable, "exec", request.respond_to?(:container_id) ? request.container_id : request[:container_id], "mkdir", "-p", "/tmp"]
420
- execute_shell_command(mkdir_cmd.join(" "))
421
-
422
- # Write script to a temporary file on host and mount it
423
- require 'tempfile'
424
- script_content = request.respond_to?(:script_content) ? request.script_content : request[:script_content]
425
- # Normalize line endings to Unix format for container execution
426
- script_content_normalized = script_content.gsub(/\r\n/, "\n").gsub(/\r/, "\n")
427
- temp_file = Tempfile.new(['script', '.sh'], binmode: true)
428
- temp_file.write(script_content_normalized)
429
- temp_file.close
430
-
431
- # Copy the script to the container
432
- copy_cmd = [@podman_executable, "cp", temp_file.path, "#{request.respond_to?(:container_id) ? request.container_id : request[:container_id]}:#{script_file}"]
433
- write_result = execute_shell_command(copy_cmd.join(" "))
434
-
435
- # Clean up temp file
436
- temp_file.unlink
437
- return create_error_response("Failed to write script") unless write_result[:success]
438
-
439
- # Make script executable
440
- chmod_cmd = [@podman_executable, "exec", request.respond_to?(:container_id) ? request.container_id : request[:container_id], "chmod", "+x", script_file]
441
- execute_shell_command(chmod_cmd.join(" "))
442
-
443
- # Execute script
444
- exec_cmd = [@podman_executable, "exec"]
445
- exec_cmd << "--workdir" << (request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]) if request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]
446
- exec_cmd << "--user" << (request.respond_to?(:user) ? request.user : request[:user]) if request.respond_to?(:user) ? request.user : request[:user]
447
-
448
- (request.respond_to?(:environment) ? request.environment : request[:environment]).each do |key, value|
449
- exec_cmd << "--env" << "#{key}=#{value}"
450
- end
451
-
452
- exec_cmd << (request.respond_to?(:container_id) ? request.container_id : request[:container_id]) << "sh" << script_file
453
-
454
- result = execute_command_with_timeout(exec_cmd.join(" "), request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds])
455
-
456
- if grpc_available?
457
- Makit::V1::Podman::Service::ExecuteScriptResponse.new(
458
- success: result[:success],
459
- exit_code: result[:exit_code],
460
- stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
461
- stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
462
- started_at: result[:started_at],
463
- finished_at: result[:finished_at],
464
- duration_seconds: result[:duration_seconds],
465
- error_message: result[:error_message]
466
- )
467
- else
468
- {
469
- success: result[:success],
470
- exit_code: result[:exit_code],
471
- stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
472
- stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
473
- started_at: result[:started_at],
474
- finished_at: result[:finished_at],
475
- duration_seconds: result[:duration_seconds],
476
- error_message: result[:error_message]
477
- }
478
- end
479
-
480
- ensure
481
- # Cleanup script file
482
- cleanup_cmd = [@podman_executable, "exec", request.respond_to?(:container_id) ? request.container_id : request[:container_id], "rm", "-f", script_file]
483
- execute_shell_command(cleanup_cmd.join(" "))
484
- end
485
- end
486
-
487
- def execute_command(request, _unused_call = nil)
488
- cmd = [@podman_executable, "exec"]
489
- working_directory = request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]
490
- user = request.respond_to?(:user) ? request.user : request[:user]
491
- environment = request.respond_to?(:environment) ? request.environment : request[:environment]
492
- container_id = request.respond_to?(:container_id) ? request.container_id : request[:container_id]
493
- command = request.respond_to?(:command) ? request.command : request[:command]
494
- timeout_seconds = request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds]
495
- capture_output = request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]
496
- capture_errors = request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]
497
-
498
- cmd << "--workdir" << working_directory if working_directory
499
- cmd << "--user" << user if user
500
-
501
- # Add environment variables
502
- (environment || {}).each do |key, value|
503
- cmd << "--env" << "#{key}=#{value}"
504
- end
505
-
506
- cmd << container_id
507
- cmd.concat(command)
508
-
509
- result = execute_command_with_timeout(cmd.join(" "), timeout_seconds)
510
-
511
- if grpc_available?
512
- Makit::V1::Podman::Service::ExecuteCommandResponse.new(
513
- success: result[:success],
514
- exit_code: result[:exit_code],
515
- stdout: capture_output ? result[:stdout] : "",
516
- stderr: capture_errors ? result[:stderr] : "",
517
- started_at: result[:started_at],
518
- finished_at: result[:finished_at],
519
- duration_seconds: result[:duration_seconds],
520
- error_message: result[:error_message]
521
- )
522
- else
523
- {
524
- success: result[:success],
525
- exit_code: result[:exit_code],
526
- stdout: capture_output ? result[:stdout] : "",
527
- stderr: capture_errors ? result[:stderr] : "",
528
- started_at: result[:started_at],
529
- finished_at: result[:finished_at],
530
- duration_seconds: result[:duration_seconds],
531
- error_message: result[:error_message]
532
- }
533
- end
534
- end
535
-
536
- def run_container(request, _unused_call = nil)
537
- cmd = [@podman_executable, "run"]
538
- auto_remove = request.respond_to?(:auto_remove) ? request.auto_remove : request[:auto_remove]
539
- interactive = request.respond_to?(:interactive) ? request.interactive : request[:interactive]
540
- tty = request.respond_to?(:tty) ? request.tty : request[:tty]
541
- environment = request.respond_to?(:environment) ? request.environment : request[:environment]
542
- volume_mounts = request.respond_to?(:volume_mounts) ? request.volume_mounts : request[:volume_mounts]
543
- port_mappings = request.respond_to?(:port_mappings) ? request.port_mappings : request[:port_mappings]
544
- labels = request.respond_to?(:labels) ? request.labels : request[:labels]
545
- image = request.respond_to?(:image) ? request.image : request[:image]
546
- command = request.respond_to?(:command) ? request.command : request[:command]
547
- args = request.respond_to?(:args) ? request.args : request[:args]
548
-
549
- cmd << "--rm" if auto_remove
550
- cmd << "--interactive" if interactive
551
- cmd << "--tty" if tty
552
-
553
- # Add environment variables
554
- (environment || {}).each do |key, value|
555
- cmd << "--env" << "#{key}=#{value}"
556
- end
557
-
558
- # Add volume mounts
559
- (volume_mounts || []).each do |mount|
560
- host_path = mount.respond_to?(:host_path) ? mount.host_path : mount[:host_path]
561
- container_path = mount.respond_to?(:container_path) ? mount.container_path : mount[:container_path]
562
- mode = mount.respond_to?(:mode) ? mount.mode : mount[:mode]
563
-
564
- # Convert Windows paths to WSL paths on Windows
565
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
566
- host_path = convert_to_wsl_path(host_path)
567
- end
568
-
569
- cmd << "--volume" << "#{host_path}:#{container_path}:#{mode}"
570
- end
571
-
572
- # Add port mappings
573
- (port_mappings || []).each do |port|
574
- host_port = port.respond_to?(:host_port) ? port.host_port : port[:host_port]
575
- container_port = port.respond_to?(:container_port) ? port.container_port : port[:container_port]
576
- protocol = port.respond_to?(:protocol) ? port.protocol : port[:protocol]
577
- cmd << "--publish" << "#{host_port}:#{container_port}/#{protocol}"
578
- end
579
-
580
- # Add labels
581
- (labels || {}).each do |key, value|
582
- cmd << "--label" << "#{key}=#{value}"
583
- end
584
-
585
- cmd << image
586
-
587
- # Add command and args
588
- if command && (command.is_a?(Array) ? command.any? : !command.empty?)
589
- if command.is_a?(String)
590
- # If command is a string, treat it as a shell command
591
- cmd << "sh" << "-c" << command
592
- elsif command.length == 3 && command[0] == "sh" && command[1] == "-c"
593
- # If the command contains shell commands with spaces, we need to quote them properly
594
- # Escape any double quotes in the command
595
- escaped_command = command[2].gsub('"', '\\"')
596
- cmd << command[0] << command[1] << "\"#{escaped_command}\""
597
- else
598
- cmd.concat(command)
599
- end
600
- end
601
- if args && args.any?
602
- cmd.concat(args)
603
- end
604
-
605
- result = execute_command_with_timeout(cmd.join(" "), request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds])
606
-
607
- if grpc_available?
608
- Makit::V1::Podman::Service::RunContainerResponse.new(
609
- success: result[:success],
610
- container_id: extract_container_id(result[:stdout]),
611
- exit_code: result[:exit_code],
612
- stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
613
- stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
614
- started_at: result[:started_at],
615
- finished_at: result[:finished_at],
616
- duration_seconds: result[:duration_seconds],
617
- error_message: result[:error_message]
618
- )
619
- else
620
- {
621
- success: result[:success],
622
- container_id: extract_container_id(result[:stdout]),
623
- exit_code: result[:exit_code],
624
- stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
625
- stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
626
- started_at: result[:started_at],
627
- finished_at: result[:finished_at],
628
- duration_seconds: result[:duration_seconds],
629
- error_message: result[:error_message]
630
- }
631
- end
632
- end
633
-
634
- # Volume operations
635
- def create_volume(request, _unused_call = nil)
636
- cmd = [@podman_executable, "volume", "create"]
637
- driver = request.respond_to?(:driver) ? request.driver : request[:driver]
638
- labels = request.respond_to?(:labels) ? request.labels : request[:labels]
639
- options = request.respond_to?(:options) ? request.options : request[:options]
640
- name = request.respond_to?(:name) ? request.name : request[:name]
641
-
642
- cmd << "--driver" << driver if driver
643
-
644
- (labels || {}).each do |key, value|
645
- cmd << "--label" << "#{key}=#{value}"
646
- end
647
-
648
- (options || {}).each do |key, value|
649
- cmd << "--opt" << "#{key}=#{value}"
650
- end
651
-
652
- cmd << name
653
-
654
- result = execute_shell_command(cmd.join(" "))
655
-
656
- if result[:success]
657
- # Get volume info
658
- inspect_cmd = [@podman_executable, "volume", "inspect", request.respond_to?(:name) ? request.name : request[:name]]
659
- inspect_result = execute_shell_command(inspect_cmd.join(" "))
660
-
661
- volume_info = if inspect_result[:success]
662
- JSON.parse(inspect_result[:stdout])[0]
663
- else
664
- {}
665
- end
666
-
667
- volume = if grpc_available?
668
- Makit::V1::Podman::Service::Volume.new(
669
- name: volume_info["Name"] || (request.respond_to?(:name) ? request.name : request[:name]),
670
- driver: volume_info["Driver"] || "local",
671
- mountpoint: volume_info["Mountpoint"] || "",
672
- created: volume_info["CreatedAt"] ? Time.parse(volume_info["CreatedAt"]) : Time.now,
673
- labels: volume_info["Labels"] || {},
674
- options: volume_info["Options"] || {}
675
- )
676
- else
677
- {
678
- name: volume_info["Name"] || (request.respond_to?(:name) ? request.name : request[:name]),
679
- driver: volume_info["Driver"] || "local",
680
- mountpoint: volume_info["Mountpoint"] || "",
681
- created: volume_info["CreatedAt"] ? Time.parse(volume_info["CreatedAt"]) : Time.now,
682
- labels: volume_info["Labels"] || {},
683
- options: volume_info["Options"] || {}
684
- }
685
- end
686
-
687
- if grpc_available?
688
- Makit::V1::Podman::Service::CreateVolumeResponse.new(
689
- success: true,
690
- volume: volume
691
- )
692
- else
693
- {
694
- success: true,
695
- volume: volume
696
- }
697
- end
698
- else
699
- if grpc_available?
700
- Makit::V1::Podman::Service::CreateVolumeResponse.new(
701
- success: false,
702
- error_message: result[:stderr]
703
- )
704
- else
705
- {
706
- success: false,
707
- error_message: result[:stderr]
708
- }
709
- end
710
- end
711
- end
712
-
713
- def list_volumes(request, _unused_call = nil)
714
- cmd = [@podman_executable, "volume", "ls", "--format", "json"]
715
-
716
- result = execute_shell_command(cmd.join(" "))
717
-
718
- if result[:success]
719
- volumes_data = JSON.parse(result[:stdout])
720
- volumes = volumes_data.map do |vol|
721
- if grpc_available?
722
- Makit::V1::Podman::Service::Volume.new(
723
- name: vol["Name"],
724
- driver: vol["Driver"],
725
- mountpoint: vol["Mountpoint"],
726
- created: vol["CreatedAt"] ? Time.parse(vol["CreatedAt"]) : Time.now,
727
- labels: vol["Labels"] || {},
728
- options: vol["Options"] || {}
729
- )
730
- else
731
- {
732
- name: vol["Name"],
733
- driver: vol["Driver"],
734
- mountpoint: vol["Mountpoint"],
735
- created: vol["CreatedAt"] ? Time.parse(vol["CreatedAt"]) : Time.now,
736
- labels: vol["Labels"] || {},
737
- options: vol["Options"] || {}
738
- }
739
- end
740
- end
741
-
742
- if grpc_available?
743
- Makit::V1::Podman::Service::ListVolumesResponse.new(
744
- volumes: volumes,
745
- total_count: volumes.length
746
- )
747
- else
748
- {
749
- volumes: volumes,
750
- total_count: volumes.length
751
- }
752
- end
753
- else
754
- if grpc_available?
755
- Makit::V1::Podman::Service::ListVolumesResponse.new(
756
- volumes: [],
757
- total_count: 0
758
- )
759
- else
760
- {
761
- volumes: [],
762
- total_count: 0
763
- }
764
- end
765
- end
766
- end
767
-
768
- def remove_volume(request, _unused_call = nil)
769
- cmd = [@podman_executable, "volume", "rm"]
770
- cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
771
- cmd << (request.respond_to?(:volume_name) ? request.volume_name : request[:volume_name])
772
-
773
- result = execute_shell_command(cmd.join(" "))
774
-
775
- if grpc_available?
776
- Makit::V1::Podman::Service::RemoveVolumeResponse.new(
777
- success: result[:success],
778
- error_message: result[:success] ? "" : result[:stderr]
779
- )
780
- else
781
- {
782
- success: result[:success],
783
- error_message: result[:success] ? "" : result[:stderr]
784
- }
785
- end
786
- end
787
-
788
- # System operations
789
- def get_version(_request, _unused_call = nil)
790
- result = execute_shell_command("#{@podman_executable} version --format json")
791
-
792
- if result[:success]
793
- version_info = JSON.parse(result[:stdout])
794
- if grpc_available?
795
- Makit::V1::Podman::Service::VersionResponse.new(
796
- version: version_info["Client"]["Version"],
797
- api_version: version_info["Client"]["APIVersion"],
798
- go_version: version_info["Client"]["GoVersion"],
799
- git_commit: version_info["Client"]["GitCommit"],
800
- build_time: version_info["Client"]["BuildTime"],
801
- os_arch: version_info["Client"]["OsArch"]
802
- )
803
- else
804
- {
805
- version: version_info["Client"]["Version"],
806
- api_version: version_info["Client"]["APIVersion"],
807
- go_version: version_info["Client"]["GoVersion"],
808
- git_commit: version_info["Client"]["GitCommit"],
809
- build_time: version_info["Client"]["BuildTime"],
810
- os_arch: version_info["Client"]["OsArch"]
811
- }
812
- end
813
- else
814
- if grpc_available?
815
- Makit::V1::Podman::Service::VersionResponse.new(
816
- version: "unknown",
817
- error_message: result[:stderr]
818
- )
819
- else
820
- {
821
- version: "unknown",
822
- error_message: result[:stderr]
823
- }
824
- end
825
- end
826
- end
827
-
828
- def get_system_info(_request, _unused_call = nil)
829
- result = execute_shell_command("#{@podman_executable} system info --format json")
830
-
831
- if result[:success]
832
- system_info = JSON.parse(result[:stdout])
833
- if grpc_available?
834
- Makit::V1::Podman::Service::SystemInfoResponse.new(
835
- hostname: system_info["hostname"] || "",
836
- kernel: system_info["kernel"] || "",
837
- os: system_info["os"] || "",
838
- arch: system_info["arch"] || "",
839
- total_memory: system_info["memTotal"] || 0,
840
- available_memory: system_info["memAvailable"] || 0,
841
- cpu_count: system_info["cpus"] || 0,
842
- storage_drivers: system_info["store"]["graphDriverName"] ? [system_info["store"]["graphDriverName"]] : [],
843
- log_drivers: system_info["logDriver"] ? [system_info["logDriver"]] : []
844
- )
845
- else
846
- {
847
- hostname: system_info["hostname"] || "",
848
- kernel: system_info["kernel"] || "",
849
- os: system_info["os"] || "",
850
- arch: system_info["arch"] || "",
851
- total_memory: system_info["memTotal"] || 0,
852
- available_memory: system_info["memAvailable"] || 0,
853
- cpu_count: system_info["cpus"] || 0,
854
- storage_drivers: system_info["store"]["graphDriverName"] ? [system_info["store"]["graphDriverName"]] : [],
855
- log_drivers: system_info["logDriver"] ? [system_info["logDriver"]] : []
856
- }
857
- end
858
- else
859
- if grpc_available?
860
- Makit::V1::Podman::Service::SystemInfoResponse.new(
861
- hostname: "",
862
- kernel: "",
863
- os: "",
864
- arch: "",
865
- total_memory: 0,
866
- available_memory: 0,
867
- cpu_count: 0,
868
- storage_drivers: [],
869
- log_drivers: []
870
- )
871
- else
872
- {
873
- hostname: "",
874
- kernel: "",
875
- os: "",
876
- arch: "",
877
- total_memory: 0,
878
- available_memory: 0,
879
- cpu_count: 0,
880
- storage_drivers: [],
881
- log_drivers: []
882
- }
883
- end
884
- end
885
- end
886
-
887
- def health_check(_request, _unused_call = nil)
888
- result = execute_shell_command("#{@podman_executable} version")
889
-
890
- if grpc_available?
891
- Makit::V1::Podman::Service::HealthCheckResponse.new(
892
- healthy: result[:success],
893
- status: result[:success] ? "healthy" : "unhealthy",
894
- error_message: result[:success] ? "" : result[:stderr],
895
- checked_at: Time.now
896
- )
897
- else
898
- {
899
- healthy: result[:success],
900
- status: result[:success] ? "healthy" : "unhealthy",
901
- error_message: result[:success] ? "" : result[:stderr],
902
- checked_at: Time.now
903
- }
904
- end
905
- end
906
-
907
- def execute_shell_command(command)
908
- start_time = Time.now
909
- output = `#{command} 2>&1`
910
- exit_code = $?.exitstatus
911
- end_time = Time.now
912
-
913
- {
914
- success: exit_code == 0,
915
- exit_code: exit_code,
916
- stdout: output,
917
- stderr: exit_code != 0 ? output : "",
918
- started_at: start_time,
919
- finished_at: end_time,
920
- duration_seconds: (end_time - start_time).to_i,
921
- error_message: exit_code != 0 ? output : ""
922
- }
923
- end
924
-
925
- private
926
-
927
- def grpc_available?
928
- self.class.grpc_available?
929
- end
930
-
931
- def execute_command_with_input(command, input)
932
- start_time = Time.now
933
-
934
- # Use Ruby's Open3 for proper input handling
935
- require 'open3'
936
-
937
- begin
938
- stdout, stderr, status = Open3.capture3(command, stdin_data: input)
939
- exit_code = status.exitstatus
940
- end_time = Time.now
941
-
942
- {
943
- success: exit_code == 0,
944
- exit_code: exit_code,
945
- stdout: stdout,
946
- stderr: stderr,
947
- started_at: start_time,
948
- finished_at: end_time,
949
- duration_seconds: (end_time - start_time).to_i,
950
- error_message: exit_code != 0 ? stderr : ""
951
- }
952
- rescue => e
953
- end_time = Time.now
954
- {
955
- success: false,
956
- exit_code: 1,
957
- stdout: "",
958
- stderr: e.message,
959
- started_at: start_time,
960
- finished_at: end_time,
961
- duration_seconds: (end_time - start_time).to_i,
962
- error_message: e.message
963
- }
964
- end
965
- end
966
-
967
- def execute_command_with_timeout(command, timeout_seconds)
968
- # For now, just execute normally
969
- # In a production implementation, you'd use proper timeout handling
970
- execute_shell_command(command)
971
- end
972
-
973
- def find_executable(executable_name)
974
- # Cross-platform executable finding logic
975
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
976
- # Windows logic
977
- # If it's already a full path, check if it exists and is executable
978
- if executable_name.include?("\\") || executable_name.include?("/")
979
- return executable_name if File.executable?(executable_name)
980
- end
981
-
982
- # Otherwise, try common extensions
983
- ["#{executable_name}.exe", "#{executable_name}.bat", "#{executable_name}.cmd"].each do |exe|
984
- return exe if system("#{exe} --version", out: File::NULL, err: File::NULL)
985
- end
986
- nil
987
- else
988
- # Unix logic
989
- # If it's already a full path, check if it exists and is executable
990
- if executable_name.include?("/")
991
- return executable_name if File.executable?(executable_name)
992
- end
993
-
994
- # Otherwise, use which
995
- `which #{executable_name}`.strip
996
- end
997
- end
998
-
999
- def extract_image_id(output)
1000
- # Extract image ID from podman pull output
1001
- output.lines.last&.strip || ""
1002
- end
1003
-
1004
- def extract_layers(output)
1005
- # Extract layer information from podman pull output
1006
- output.lines.select { |line| line.include?("Pulling") }.map(&:strip)
1007
- end
1008
-
1009
- def extract_container_id(output)
1010
- # Extract container ID from podman run output
1011
- output.strip
1012
- end
1013
-
1014
- def map_container_status(status)
1015
- case status.downcase
1016
- when "created"
1017
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::CREATED : :created
1018
- when "running"
1019
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::RUNNING : :running
1020
- when "paused"
1021
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::PAUSED : :paused
1022
- when "restarting"
1023
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::RESTARTING : :restarting
1024
- when "removing"
1025
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::REMOVING : :removing
1026
- when "exited"
1027
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::EXITED : :exited
1028
- when "dead"
1029
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::DEAD : :dead
1030
- else
1031
- grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::UNKNOWN : :unknown
1032
- end
1033
- end
1034
-
1035
- def parse_time(time_string)
1036
- return Time.now if time_string.nil?
1037
-
1038
- # Handle integers (Unix timestamps)
1039
- if time_string.is_a?(Integer)
1040
- return Time.at(time_string)
1041
- end
1042
-
1043
- # Handle empty strings
1044
- return Time.now if time_string.empty?
1045
-
1046
- # Handle relative time strings like "Less than a second ago"
1047
- if time_string.include?("ago")
1048
- return Time.now
1049
- end
1050
-
1051
- begin
1052
- Time.parse(time_string)
1053
- rescue ArgumentError
1054
- Time.now
1055
- end
1056
- end
1057
-
1058
- def create_error_response(message)
1059
- if grpc_available?
1060
- Makit::V1::Podman::Service::ExecuteScriptResponse.new(
1061
- success: false,
1062
- error_message: message,
1063
- exit_code: 1
1064
- )
1065
- else
1066
- {
1067
- success: false,
1068
- error_message: message,
1069
- exit_code: 1
1070
- }
1071
- end
1072
- end
1073
-
1074
- def convert_to_wsl_path(windows_path)
1075
- # On Windows, Podman handles Windows paths natively
1076
- # Just convert forward slashes to backslashes if needed
1077
- windows_path.tr('/', '\\')
1078
- end
1079
- end
1080
- end
1081
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+ require "securerandom"
6
+
7
+ module Makit
8
+ module Podman
9
+ class PodmanServiceImpl
10
+ def initialize(podman_executable: "podman")
11
+ @podman_executable = find_executable(podman_executable)
12
+ raise "Podman executable not found" unless @podman_executable
13
+ end
14
+
15
+ # Check if gRPC service is available
16
+ def self.grpc_available?
17
+ defined?(Makit::V1::Makit::V1::Podman::Service::PodmanService)
18
+ end
19
+
20
+ # Image operations
21
+ def pull_image(request, _unused_call = nil)
22
+ cmd = [@podman_executable, "pull"]
23
+ cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
24
+ cmd << "#{request.respond_to?(:image_name) ? request.image_name : request[:image_name]}:#{request.respond_to?(:tag) ? request.tag : request[:tag]}"
25
+
26
+ result = execute_shell_command(cmd.join(" "))
27
+
28
+ if result[:success]
29
+ # Extract image ID from output
30
+ image_id = extract_image_id(result[:stdout])
31
+ if grpc_available?
32
+ Makit::V1::Podman::Service::PullImageResponse.new(
33
+ success: true,
34
+ image_id: image_id,
35
+ layers: extract_layers(result[:stdout]),
36
+ pulled_at: Time.now
37
+ )
38
+ else
39
+ {
40
+ success: true,
41
+ image_id: image_id,
42
+ layers: extract_layers(result[:stdout]),
43
+ pulled_at: Time.now
44
+ }
45
+ end
46
+ else
47
+ if grpc_available?
48
+ Makit::V1::Podman::Service::PullImageResponse.new(
49
+ success: false,
50
+ error_message: result[:stderr]
51
+ )
52
+ else
53
+ {
54
+ success: false,
55
+ error_message: result[:stderr]
56
+ }
57
+ end
58
+ end
59
+ end
60
+
61
+ def list_images(request, _unused_call = nil)
62
+ cmd = [@podman_executable, "images", "--format", "json"]
63
+ cmd << "--all" if request.respond_to?(:all) ? request.all : request[:all]
64
+
65
+ result = execute_shell_command(cmd.join(" "))
66
+
67
+ if result[:success]
68
+ images_data = JSON.parse(result[:stdout])
69
+ images = images_data.map do |img|
70
+ # Extract repository and tag from Names array
71
+ names = img["Names"] || []
72
+ repository = "unknown"
73
+ tag = "latest"
74
+
75
+ if names.any?
76
+ name_parts = names.first.split(":")
77
+ repository = name_parts[0]
78
+ tag = name_parts[1] || "latest"
79
+ end
80
+
81
+ created_time = if img["Created"].is_a?(String)
82
+ Time.parse(img["Created"])
83
+ else
84
+ Time.at(img["Created"])
85
+ end
86
+
87
+ if grpc_available?
88
+ Makit::V1::Podman::Service::Image.new(
89
+ id: img["Id"],
90
+ repository: repository,
91
+ tag: tag,
92
+ digest: img["Digest"],
93
+ created: created_time,
94
+ size: img["Size"].to_i,
95
+ labels: img["Labels"] || {}
96
+ )
97
+ else
98
+ {
99
+ id: img["Id"],
100
+ repository: repository,
101
+ tag: tag,
102
+ digest: img["Digest"],
103
+ created: created_time,
104
+ size: img["Size"].to_i,
105
+ labels: img["Labels"] || {}
106
+ }
107
+ end
108
+ end
109
+
110
+ if grpc_available?
111
+ Makit::V1::Podman::Service::ListImagesResponse.new(
112
+ images: images,
113
+ total_count: images.length
114
+ )
115
+ else
116
+ {
117
+ images: images,
118
+ total_count: images.length
119
+ }
120
+ end
121
+ else
122
+ if grpc_available?
123
+ Makit::V1::Podman::Service::ListImagesResponse.new(
124
+ images: [],
125
+ total_count: 0
126
+ )
127
+ else
128
+ {
129
+ images: [],
130
+ total_count: 0
131
+ }
132
+ end
133
+ end
134
+ end
135
+
136
+ def remove_image(request, _unused_call = nil)
137
+ cmd = [@podman_executable, "rmi"]
138
+ cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
139
+ cmd << (request.respond_to?(:image_id) ? request.image_id : request[:image_id])
140
+
141
+ result = execute_shell_command(cmd.join(" "))
142
+
143
+ if grpc_available?
144
+ Makit::V1::Podman::Service::RemoveImageResponse.new(
145
+ success: result[:success],
146
+ error_message: result[:success] ? "" : result[:stderr]
147
+ )
148
+ else
149
+ {
150
+ success: result[:success],
151
+ error_message: result[:success] ? "" : result[:stderr]
152
+ }
153
+ end
154
+ end
155
+
156
+ def inspect_image(request, _unused_call = nil)
157
+ cmd = [@podman_executable, "inspect", request.respond_to?(:image_id) ? request.image_id : request[:image_id]]
158
+ result = execute_shell_command(cmd.join(" "))
159
+
160
+ if grpc_available?
161
+ Makit::V1::Podman::Service::InspectImageResponse.new(
162
+ success: result[:success],
163
+ image_info: result[:success] ? result[:stdout] : "",
164
+ error_message: result[:success] ? "" : result[:stderr]
165
+ )
166
+ else
167
+ {
168
+ success: result[:success],
169
+ image_info: result[:success] ? result[:stdout] : "",
170
+ error_message: result[:success] ? "" : result[:stderr]
171
+ }
172
+ end
173
+ end
174
+
175
+ # Container operations
176
+ def create_container(request, _unused_call = nil)
177
+ cmd = [@podman_executable, "create"]
178
+ name = request.respond_to?(:name) ? request.name : request[:name]
179
+ working_directory = request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]
180
+ user = request.respond_to?(:user) ? request.user : request[:user]
181
+ privileged = request.respond_to?(:privileged) ? request.privileged : request[:privileged]
182
+ auto_remove = request.respond_to?(:auto_remove) ? request.auto_remove : request[:auto_remove]
183
+ environment = request.respond_to?(:environment) ? request.environment : request[:environment]
184
+ volume_mounts = request.respond_to?(:volume_mounts) ? request.volume_mounts : request[:volume_mounts]
185
+ port_mappings = request.respond_to?(:port_mappings) ? request.port_mappings : request[:port_mappings]
186
+ labels = request.respond_to?(:labels) ? request.labels : request[:labels]
187
+ image = request.respond_to?(:image) ? request.image : request[:image]
188
+ command = request.respond_to?(:command) ? request.command : request[:command]
189
+ args = request.respond_to?(:args) ? request.args : request[:args]
190
+
191
+ cmd << "--name" << name if name
192
+ cmd << "--workdir" << working_directory if working_directory
193
+ cmd << "--user" << user if user
194
+ cmd << "--privileged" if privileged
195
+ cmd << "--rm" if auto_remove
196
+
197
+ # Add environment variables
198
+ (environment || {}).each do |key, value|
199
+ cmd << "--env" << "#{key}=#{value}"
200
+ end
201
+
202
+ # Add volume mounts
203
+ (volume_mounts || []).each do |mount|
204
+ host_path = mount.respond_to?(:host_path) ? mount.host_path : mount[:host_path]
205
+ container_path = mount.respond_to?(:container_path) ? mount.container_path : mount[:container_path]
206
+ mode = mount.respond_to?(:mode) ? mount.mode : mount[:mode]
207
+
208
+ # Convert Windows paths to WSL paths on Windows
209
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
210
+ host_path = convert_to_wsl_path(host_path)
211
+ end
212
+
213
+ cmd << "--volume" << "#{host_path}:#{container_path}:#{mode}"
214
+ end
215
+
216
+ # Add port mappings
217
+ (port_mappings || []).each do |port|
218
+ host_port = port.respond_to?(:host_port) ? port.host_port : port[:host_port]
219
+ container_port = port.respond_to?(:container_port) ? port.container_port : port[:container_port]
220
+ protocol = port.respond_to?(:protocol) ? port.protocol : port[:protocol]
221
+ cmd << "--publish" << "#{host_port}:#{container_port}/#{protocol}"
222
+ end
223
+
224
+ # Add labels
225
+ (labels || {}).each do |key, value|
226
+ cmd << "--label" << "#{key}=#{value}"
227
+ end
228
+
229
+ cmd << image
230
+
231
+ # Add command and args
232
+ if command && command.any?
233
+ cmd.concat(command)
234
+ end
235
+ if args && args.any?
236
+ cmd.concat(args)
237
+ end
238
+
239
+ result = execute_shell_command(cmd.join(" "))
240
+
241
+ if result[:success]
242
+ container_id = result[:stdout].strip
243
+ if grpc_available?
244
+ Makit::V1::Podman::Service::CreateContainerResponse.new(
245
+ success: true,
246
+ container_id: container_id,
247
+ container_name: request.respond_to?(:name) ? request.name : request[:name]
248
+ )
249
+ else
250
+ {
251
+ success: true,
252
+ container_id: container_id,
253
+ container_name: request.respond_to?(:name) ? request.name : request[:name]
254
+ }
255
+ end
256
+ else
257
+ if grpc_available?
258
+ Makit::V1::Podman::Service::CreateContainerResponse.new(
259
+ success: false,
260
+ error_message: result[:stderr]
261
+ )
262
+ else
263
+ {
264
+ success: false,
265
+ error_message: result[:stderr]
266
+ }
267
+ end
268
+ end
269
+ end
270
+
271
+ def start_container(request, _unused_call = nil)
272
+ cmd = [@podman_executable, "start", request.respond_to?(:container_id) ? request.container_id : request[:container_id]]
273
+ result = execute_shell_command(cmd.join(" "))
274
+
275
+ if grpc_available?
276
+ Makit::V1::Podman::Service::StartContainerResponse.new(
277
+ success: result[:success],
278
+ error_message: result[:success] ? "" : result[:stderr]
279
+ )
280
+ else
281
+ {
282
+ success: result[:success],
283
+ error_message: result[:success] ? "" : result[:stderr]
284
+ }
285
+ end
286
+ end
287
+
288
+ def stop_container(request, _unused_call = nil)
289
+ cmd = [@podman_executable, "stop"]
290
+ timeout_seconds = request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds]
291
+ cmd << "--time" << timeout_seconds.to_s if timeout_seconds && timeout_seconds > 0
292
+ cmd << (request.respond_to?(:container_id) ? request.container_id : request[:container_id])
293
+
294
+ result = execute_shell_command(cmd.join(" "))
295
+
296
+ if grpc_available?
297
+ Makit::V1::Podman::Service::StopContainerResponse.new(
298
+ success: result[:success],
299
+ error_message: result[:success] ? "" : result[:stderr]
300
+ )
301
+ else
302
+ {
303
+ success: result[:success],
304
+ error_message: result[:success] ? "" : result[:stderr]
305
+ }
306
+ end
307
+ end
308
+
309
+ def remove_container(request, _unused_call = nil)
310
+ cmd = [@podman_executable, "rm"]
311
+ cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
312
+ cmd << (request.respond_to?(:container_id) ? request.container_id : request[:container_id])
313
+
314
+ result = execute_shell_command(cmd.join(" "))
315
+
316
+ if grpc_available?
317
+ Makit::V1::Podman::Service::RemoveContainerResponse.new(
318
+ success: result[:success],
319
+ error_message: result[:success] ? "" : result[:stderr]
320
+ )
321
+ else
322
+ {
323
+ success: result[:success],
324
+ error_message: result[:success] ? "" : result[:stderr]
325
+ }
326
+ end
327
+ end
328
+
329
+ def list_containers(request, _unused_call = nil)
330
+ cmd = [@podman_executable, "ps", "--format", "json"]
331
+ cmd << "--all" if request.respond_to?(:all) ? request.all : request[:all]
332
+
333
+ result = execute_shell_command(cmd.join(" "))
334
+
335
+ if result[:success]
336
+ containers_data = JSON.parse(result[:stdout])
337
+ containers = containers_data.map do |container|
338
+ if grpc_available?
339
+ Makit::V1::Podman::Service::Container.new(
340
+ id: container["Id"],
341
+ name: container["Names"][0],
342
+ image: container["Image"],
343
+ status: map_container_status(container["State"]),
344
+ created: parse_time(container["CreatedAt"]),
345
+ started: container["StartedAt"] ? parse_time(container["StartedAt"]) : nil,
346
+ finished: container["FinishedAt"] ? parse_time(container["FinishedAt"]) : nil,
347
+ command: container["Command"] ? [container["Command"]] : [],
348
+ environment: container["Env"] || {},
349
+ labels: container["Labels"] || {}
350
+ )
351
+ else
352
+ {
353
+ id: container["Id"],
354
+ name: container["Names"][0],
355
+ image: container["Image"],
356
+ status: map_container_status(container["State"]),
357
+ created: parse_time(container["CreatedAt"]),
358
+ started: container["StartedAt"] ? parse_time(container["StartedAt"]) : nil,
359
+ finished: container["FinishedAt"] ? parse_time(container["FinishedAt"]) : nil,
360
+ command: container["Command"] ? [container["Command"]] : [],
361
+ environment: container["Env"] || {},
362
+ labels: container["Labels"] || {}
363
+ }
364
+ end
365
+ end
366
+
367
+ if grpc_available?
368
+ Makit::V1::Podman::Service::ListContainersResponse.new(
369
+ containers: containers,
370
+ total_count: containers.length
371
+ )
372
+ else
373
+ {
374
+ containers: containers,
375
+ total_count: containers.length
376
+ }
377
+ end
378
+ else
379
+ if grpc_available?
380
+ Makit::V1::Podman::Service::ListContainersResponse.new(
381
+ containers: [],
382
+ total_count: 0
383
+ )
384
+ else
385
+ {
386
+ containers: [],
387
+ total_count: 0
388
+ }
389
+ end
390
+ end
391
+ end
392
+
393
+ def inspect_container(request, _unused_call = nil)
394
+ cmd = [@podman_executable, "inspect", request.respond_to?(:container_id) ? request.container_id : request[:container_id]]
395
+ result = execute_shell_command(cmd.join(" "))
396
+
397
+ if grpc_available?
398
+ Makit::V1::Podman::Service::InspectContainerResponse.new(
399
+ success: result[:success],
400
+ container_info: result[:success] ? result[:stdout] : "",
401
+ error_message: result[:success] ? "" : result[:stderr]
402
+ )
403
+ else
404
+ {
405
+ success: result[:success],
406
+ container_info: result[:success] ? result[:stdout] : "",
407
+ error_message: result[:success] ? "" : result[:stderr]
408
+ }
409
+ end
410
+ end
411
+
412
+ # Execution operations
413
+ def execute_script(request, _unused_call = nil)
414
+ # Create temporary script file
415
+ script_file = "/tmp/#{SecureRandom.uuid}.sh"
416
+
417
+ begin
418
+ # Ensure /tmp directory exists
419
+ mkdir_cmd = [@podman_executable, "exec", request.respond_to?(:container_id) ? request.container_id : request[:container_id], "mkdir", "-p", "/tmp"]
420
+ execute_shell_command(mkdir_cmd.join(" "))
421
+
422
+ # Write script to a temporary file on host and mount it
423
+ require 'tempfile'
424
+ script_content = request.respond_to?(:script_content) ? request.script_content : request[:script_content]
425
+ # Normalize line endings to Unix format for container execution
426
+ script_content_normalized = script_content.gsub(/\r\n/, "\n").gsub(/\r/, "\n")
427
+ temp_file = Tempfile.new(['script', '.sh'], binmode: true)
428
+ temp_file.write(script_content_normalized)
429
+ temp_file.close
430
+
431
+ # Copy the script to the container
432
+ copy_cmd = [@podman_executable, "cp", temp_file.path, "#{request.respond_to?(:container_id) ? request.container_id : request[:container_id]}:#{script_file}"]
433
+ write_result = execute_shell_command(copy_cmd.join(" "))
434
+
435
+ # Clean up temp file
436
+ temp_file.unlink
437
+ return create_error_response("Failed to write script") unless write_result[:success]
438
+
439
+ # Make script executable
440
+ chmod_cmd = [@podman_executable, "exec", request.respond_to?(:container_id) ? request.container_id : request[:container_id], "chmod", "+x", script_file]
441
+ execute_shell_command(chmod_cmd.join(" "))
442
+
443
+ # Execute script
444
+ exec_cmd = [@podman_executable, "exec"]
445
+ exec_cmd << "--workdir" << (request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]) if request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]
446
+ exec_cmd << "--user" << (request.respond_to?(:user) ? request.user : request[:user]) if request.respond_to?(:user) ? request.user : request[:user]
447
+
448
+ (request.respond_to?(:environment) ? request.environment : request[:environment]).each do |key, value|
449
+ exec_cmd << "--env" << "#{key}=#{value}"
450
+ end
451
+
452
+ exec_cmd << (request.respond_to?(:container_id) ? request.container_id : request[:container_id]) << "sh" << script_file
453
+
454
+ result = execute_command_with_timeout(exec_cmd.join(" "), request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds])
455
+
456
+ if grpc_available?
457
+ Makit::V1::Podman::Service::ExecuteScriptResponse.new(
458
+ success: result[:success],
459
+ exit_code: result[:exit_code],
460
+ stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
461
+ stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
462
+ started_at: result[:started_at],
463
+ finished_at: result[:finished_at],
464
+ duration_seconds: result[:duration_seconds],
465
+ error_message: result[:error_message]
466
+ )
467
+ else
468
+ {
469
+ success: result[:success],
470
+ exit_code: result[:exit_code],
471
+ stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
472
+ stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
473
+ started_at: result[:started_at],
474
+ finished_at: result[:finished_at],
475
+ duration_seconds: result[:duration_seconds],
476
+ error_message: result[:error_message]
477
+ }
478
+ end
479
+
480
+ ensure
481
+ # Cleanup script file
482
+ cleanup_cmd = [@podman_executable, "exec", request.respond_to?(:container_id) ? request.container_id : request[:container_id], "rm", "-f", script_file]
483
+ execute_shell_command(cleanup_cmd.join(" "))
484
+ end
485
+ end
486
+
487
+ def execute_command(request, _unused_call = nil)
488
+ cmd = [@podman_executable, "exec"]
489
+ working_directory = request.respond_to?(:working_directory) ? request.working_directory : request[:working_directory]
490
+ user = request.respond_to?(:user) ? request.user : request[:user]
491
+ environment = request.respond_to?(:environment) ? request.environment : request[:environment]
492
+ container_id = request.respond_to?(:container_id) ? request.container_id : request[:container_id]
493
+ command = request.respond_to?(:command) ? request.command : request[:command]
494
+ timeout_seconds = request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds]
495
+ capture_output = request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]
496
+ capture_errors = request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]
497
+
498
+ cmd << "--workdir" << working_directory if working_directory
499
+ cmd << "--user" << user if user
500
+
501
+ # Add environment variables
502
+ (environment || {}).each do |key, value|
503
+ cmd << "--env" << "#{key}=#{value}"
504
+ end
505
+
506
+ cmd << container_id
507
+ cmd.concat(command)
508
+
509
+ result = execute_command_with_timeout(cmd.join(" "), timeout_seconds)
510
+
511
+ if grpc_available?
512
+ Makit::V1::Podman::Service::ExecuteCommandResponse.new(
513
+ success: result[:success],
514
+ exit_code: result[:exit_code],
515
+ stdout: capture_output ? result[:stdout] : "",
516
+ stderr: capture_errors ? result[:stderr] : "",
517
+ started_at: result[:started_at],
518
+ finished_at: result[:finished_at],
519
+ duration_seconds: result[:duration_seconds],
520
+ error_message: result[:error_message]
521
+ )
522
+ else
523
+ {
524
+ success: result[:success],
525
+ exit_code: result[:exit_code],
526
+ stdout: capture_output ? result[:stdout] : "",
527
+ stderr: capture_errors ? result[:stderr] : "",
528
+ started_at: result[:started_at],
529
+ finished_at: result[:finished_at],
530
+ duration_seconds: result[:duration_seconds],
531
+ error_message: result[:error_message]
532
+ }
533
+ end
534
+ end
535
+
536
+ def run_container(request, _unused_call = nil)
537
+ cmd = [@podman_executable, "run"]
538
+ auto_remove = request.respond_to?(:auto_remove) ? request.auto_remove : request[:auto_remove]
539
+ interactive = request.respond_to?(:interactive) ? request.interactive : request[:interactive]
540
+ tty = request.respond_to?(:tty) ? request.tty : request[:tty]
541
+ environment = request.respond_to?(:environment) ? request.environment : request[:environment]
542
+ volume_mounts = request.respond_to?(:volume_mounts) ? request.volume_mounts : request[:volume_mounts]
543
+ port_mappings = request.respond_to?(:port_mappings) ? request.port_mappings : request[:port_mappings]
544
+ labels = request.respond_to?(:labels) ? request.labels : request[:labels]
545
+ image = request.respond_to?(:image) ? request.image : request[:image]
546
+ command = request.respond_to?(:command) ? request.command : request[:command]
547
+ args = request.respond_to?(:args) ? request.args : request[:args]
548
+
549
+ cmd << "--rm" if auto_remove
550
+ cmd << "--interactive" if interactive
551
+ cmd << "--tty" if tty
552
+
553
+ # Add environment variables
554
+ (environment || {}).each do |key, value|
555
+ cmd << "--env" << "#{key}=#{value}"
556
+ end
557
+
558
+ # Add volume mounts
559
+ (volume_mounts || []).each do |mount|
560
+ host_path = mount.respond_to?(:host_path) ? mount.host_path : mount[:host_path]
561
+ container_path = mount.respond_to?(:container_path) ? mount.container_path : mount[:container_path]
562
+ mode = mount.respond_to?(:mode) ? mount.mode : mount[:mode]
563
+
564
+ # Convert Windows paths to WSL paths on Windows
565
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
566
+ host_path = convert_to_wsl_path(host_path)
567
+ end
568
+
569
+ cmd << "--volume" << "#{host_path}:#{container_path}:#{mode}"
570
+ end
571
+
572
+ # Add port mappings
573
+ (port_mappings || []).each do |port|
574
+ host_port = port.respond_to?(:host_port) ? port.host_port : port[:host_port]
575
+ container_port = port.respond_to?(:container_port) ? port.container_port : port[:container_port]
576
+ protocol = port.respond_to?(:protocol) ? port.protocol : port[:protocol]
577
+ cmd << "--publish" << "#{host_port}:#{container_port}/#{protocol}"
578
+ end
579
+
580
+ # Add labels
581
+ (labels || {}).each do |key, value|
582
+ cmd << "--label" << "#{key}=#{value}"
583
+ end
584
+
585
+ cmd << image
586
+
587
+ # Add command and args
588
+ if command && (command.is_a?(Array) ? command.any? : !command.empty?)
589
+ if command.is_a?(String)
590
+ # If command is a string, treat it as a shell command
591
+ cmd << "sh" << "-c" << command
592
+ elsif command.length == 3 && command[0] == "sh" && command[1] == "-c"
593
+ # If the command contains shell commands with spaces, we need to quote them properly
594
+ # Escape any double quotes in the command
595
+ escaped_command = command[2].gsub('"', '\\"')
596
+ cmd << command[0] << command[1] << "\"#{escaped_command}\""
597
+ else
598
+ cmd.concat(command)
599
+ end
600
+ end
601
+ if args && args.any?
602
+ cmd.concat(args)
603
+ end
604
+
605
+ result = execute_command_with_timeout(cmd.join(" "), request.respond_to?(:timeout_seconds) ? request.timeout_seconds : request[:timeout_seconds])
606
+
607
+ if grpc_available?
608
+ Makit::V1::Podman::Service::RunContainerResponse.new(
609
+ success: result[:success],
610
+ container_id: extract_container_id(result[:stdout]),
611
+ exit_code: result[:exit_code],
612
+ stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
613
+ stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
614
+ started_at: result[:started_at],
615
+ finished_at: result[:finished_at],
616
+ duration_seconds: result[:duration_seconds],
617
+ error_message: result[:error_message]
618
+ )
619
+ else
620
+ {
621
+ success: result[:success],
622
+ container_id: extract_container_id(result[:stdout]),
623
+ exit_code: result[:exit_code],
624
+ stdout: (request.respond_to?(:capture_output) ? request.capture_output : request[:capture_output]) ? result[:stdout] : "",
625
+ stderr: (request.respond_to?(:capture_errors) ? request.capture_errors : request[:capture_errors]) ? result[:stderr] : "",
626
+ started_at: result[:started_at],
627
+ finished_at: result[:finished_at],
628
+ duration_seconds: result[:duration_seconds],
629
+ error_message: result[:error_message]
630
+ }
631
+ end
632
+ end
633
+
634
+ # Volume operations
635
+ def create_volume(request, _unused_call = nil)
636
+ cmd = [@podman_executable, "volume", "create"]
637
+ driver = request.respond_to?(:driver) ? request.driver : request[:driver]
638
+ labels = request.respond_to?(:labels) ? request.labels : request[:labels]
639
+ options = request.respond_to?(:options) ? request.options : request[:options]
640
+ name = request.respond_to?(:name) ? request.name : request[:name]
641
+
642
+ cmd << "--driver" << driver if driver
643
+
644
+ (labels || {}).each do |key, value|
645
+ cmd << "--label" << "#{key}=#{value}"
646
+ end
647
+
648
+ (options || {}).each do |key, value|
649
+ cmd << "--opt" << "#{key}=#{value}"
650
+ end
651
+
652
+ cmd << name
653
+
654
+ result = execute_shell_command(cmd.join(" "))
655
+
656
+ if result[:success]
657
+ # Get volume info
658
+ inspect_cmd = [@podman_executable, "volume", "inspect", request.respond_to?(:name) ? request.name : request[:name]]
659
+ inspect_result = execute_shell_command(inspect_cmd.join(" "))
660
+
661
+ volume_info = if inspect_result[:success]
662
+ JSON.parse(inspect_result[:stdout])[0]
663
+ else
664
+ {}
665
+ end
666
+
667
+ volume = if grpc_available?
668
+ Makit::V1::Podman::Service::Volume.new(
669
+ name: volume_info["Name"] || (request.respond_to?(:name) ? request.name : request[:name]),
670
+ driver: volume_info["Driver"] || "local",
671
+ mountpoint: volume_info["Mountpoint"] || "",
672
+ created: volume_info["CreatedAt"] ? Time.parse(volume_info["CreatedAt"]) : Time.now,
673
+ labels: volume_info["Labels"] || {},
674
+ options: volume_info["Options"] || {}
675
+ )
676
+ else
677
+ {
678
+ name: volume_info["Name"] || (request.respond_to?(:name) ? request.name : request[:name]),
679
+ driver: volume_info["Driver"] || "local",
680
+ mountpoint: volume_info["Mountpoint"] || "",
681
+ created: volume_info["CreatedAt"] ? Time.parse(volume_info["CreatedAt"]) : Time.now,
682
+ labels: volume_info["Labels"] || {},
683
+ options: volume_info["Options"] || {}
684
+ }
685
+ end
686
+
687
+ if grpc_available?
688
+ Makit::V1::Podman::Service::CreateVolumeResponse.new(
689
+ success: true,
690
+ volume: volume
691
+ )
692
+ else
693
+ {
694
+ success: true,
695
+ volume: volume
696
+ }
697
+ end
698
+ else
699
+ if grpc_available?
700
+ Makit::V1::Podman::Service::CreateVolumeResponse.new(
701
+ success: false,
702
+ error_message: result[:stderr]
703
+ )
704
+ else
705
+ {
706
+ success: false,
707
+ error_message: result[:stderr]
708
+ }
709
+ end
710
+ end
711
+ end
712
+
713
+ def list_volumes(request, _unused_call = nil)
714
+ cmd = [@podman_executable, "volume", "ls", "--format", "json"]
715
+
716
+ result = execute_shell_command(cmd.join(" "))
717
+
718
+ if result[:success]
719
+ volumes_data = JSON.parse(result[:stdout])
720
+ volumes = volumes_data.map do |vol|
721
+ if grpc_available?
722
+ Makit::V1::Podman::Service::Volume.new(
723
+ name: vol["Name"],
724
+ driver: vol["Driver"],
725
+ mountpoint: vol["Mountpoint"],
726
+ created: vol["CreatedAt"] ? Time.parse(vol["CreatedAt"]) : Time.now,
727
+ labels: vol["Labels"] || {},
728
+ options: vol["Options"] || {}
729
+ )
730
+ else
731
+ {
732
+ name: vol["Name"],
733
+ driver: vol["Driver"],
734
+ mountpoint: vol["Mountpoint"],
735
+ created: vol["CreatedAt"] ? Time.parse(vol["CreatedAt"]) : Time.now,
736
+ labels: vol["Labels"] || {},
737
+ options: vol["Options"] || {}
738
+ }
739
+ end
740
+ end
741
+
742
+ if grpc_available?
743
+ Makit::V1::Podman::Service::ListVolumesResponse.new(
744
+ volumes: volumes,
745
+ total_count: volumes.length
746
+ )
747
+ else
748
+ {
749
+ volumes: volumes,
750
+ total_count: volumes.length
751
+ }
752
+ end
753
+ else
754
+ if grpc_available?
755
+ Makit::V1::Podman::Service::ListVolumesResponse.new(
756
+ volumes: [],
757
+ total_count: 0
758
+ )
759
+ else
760
+ {
761
+ volumes: [],
762
+ total_count: 0
763
+ }
764
+ end
765
+ end
766
+ end
767
+
768
+ def remove_volume(request, _unused_call = nil)
769
+ cmd = [@podman_executable, "volume", "rm"]
770
+ cmd << "--force" if request.respond_to?(:force) ? request.force : request[:force]
771
+ cmd << (request.respond_to?(:volume_name) ? request.volume_name : request[:volume_name])
772
+
773
+ result = execute_shell_command(cmd.join(" "))
774
+
775
+ if grpc_available?
776
+ Makit::V1::Podman::Service::RemoveVolumeResponse.new(
777
+ success: result[:success],
778
+ error_message: result[:success] ? "" : result[:stderr]
779
+ )
780
+ else
781
+ {
782
+ success: result[:success],
783
+ error_message: result[:success] ? "" : result[:stderr]
784
+ }
785
+ end
786
+ end
787
+
788
+ # System operations
789
+ def get_version(_request, _unused_call = nil)
790
+ result = execute_shell_command("#{@podman_executable} version --format json")
791
+
792
+ if result[:success]
793
+ version_info = JSON.parse(result[:stdout])
794
+ if grpc_available?
795
+ Makit::V1::Podman::Service::VersionResponse.new(
796
+ version: version_info["Client"]["Version"],
797
+ api_version: version_info["Client"]["APIVersion"],
798
+ go_version: version_info["Client"]["GoVersion"],
799
+ git_commit: version_info["Client"]["GitCommit"],
800
+ build_time: version_info["Client"]["BuildTime"],
801
+ os_arch: version_info["Client"]["OsArch"]
802
+ )
803
+ else
804
+ {
805
+ version: version_info["Client"]["Version"],
806
+ api_version: version_info["Client"]["APIVersion"],
807
+ go_version: version_info["Client"]["GoVersion"],
808
+ git_commit: version_info["Client"]["GitCommit"],
809
+ build_time: version_info["Client"]["BuildTime"],
810
+ os_arch: version_info["Client"]["OsArch"]
811
+ }
812
+ end
813
+ else
814
+ if grpc_available?
815
+ Makit::V1::Podman::Service::VersionResponse.new(
816
+ version: "unknown",
817
+ error_message: result[:stderr]
818
+ )
819
+ else
820
+ {
821
+ version: "unknown",
822
+ error_message: result[:stderr]
823
+ }
824
+ end
825
+ end
826
+ end
827
+
828
+ def get_system_info(_request, _unused_call = nil)
829
+ result = execute_shell_command("#{@podman_executable} system info --format json")
830
+
831
+ if result[:success]
832
+ system_info = JSON.parse(result[:stdout])
833
+ if grpc_available?
834
+ Makit::V1::Podman::Service::SystemInfoResponse.new(
835
+ hostname: system_info["hostname"] || "",
836
+ kernel: system_info["kernel"] || "",
837
+ os: system_info["os"] || "",
838
+ arch: system_info["arch"] || "",
839
+ total_memory: system_info["memTotal"] || 0,
840
+ available_memory: system_info["memAvailable"] || 0,
841
+ cpu_count: system_info["cpus"] || 0,
842
+ storage_drivers: system_info["store"]["graphDriverName"] ? [system_info["store"]["graphDriverName"]] : [],
843
+ log_drivers: system_info["logDriver"] ? [system_info["logDriver"]] : []
844
+ )
845
+ else
846
+ {
847
+ hostname: system_info["hostname"] || "",
848
+ kernel: system_info["kernel"] || "",
849
+ os: system_info["os"] || "",
850
+ arch: system_info["arch"] || "",
851
+ total_memory: system_info["memTotal"] || 0,
852
+ available_memory: system_info["memAvailable"] || 0,
853
+ cpu_count: system_info["cpus"] || 0,
854
+ storage_drivers: system_info["store"]["graphDriverName"] ? [system_info["store"]["graphDriverName"]] : [],
855
+ log_drivers: system_info["logDriver"] ? [system_info["logDriver"]] : []
856
+ }
857
+ end
858
+ else
859
+ if grpc_available?
860
+ Makit::V1::Podman::Service::SystemInfoResponse.new(
861
+ hostname: "",
862
+ kernel: "",
863
+ os: "",
864
+ arch: "",
865
+ total_memory: 0,
866
+ available_memory: 0,
867
+ cpu_count: 0,
868
+ storage_drivers: [],
869
+ log_drivers: []
870
+ )
871
+ else
872
+ {
873
+ hostname: "",
874
+ kernel: "",
875
+ os: "",
876
+ arch: "",
877
+ total_memory: 0,
878
+ available_memory: 0,
879
+ cpu_count: 0,
880
+ storage_drivers: [],
881
+ log_drivers: []
882
+ }
883
+ end
884
+ end
885
+ end
886
+
887
+ def health_check(_request, _unused_call = nil)
888
+ result = execute_shell_command("#{@podman_executable} version")
889
+
890
+ if grpc_available?
891
+ Makit::V1::Podman::Service::HealthCheckResponse.new(
892
+ healthy: result[:success],
893
+ status: result[:success] ? "healthy" : "unhealthy",
894
+ error_message: result[:success] ? "" : result[:stderr],
895
+ checked_at: Time.now
896
+ )
897
+ else
898
+ {
899
+ healthy: result[:success],
900
+ status: result[:success] ? "healthy" : "unhealthy",
901
+ error_message: result[:success] ? "" : result[:stderr],
902
+ checked_at: Time.now
903
+ }
904
+ end
905
+ end
906
+
907
+ def execute_shell_command(command)
908
+ start_time = Time.now
909
+ output = `#{command} 2>&1`
910
+ exit_code = $?.exitstatus
911
+ end_time = Time.now
912
+
913
+ {
914
+ success: exit_code == 0,
915
+ exit_code: exit_code,
916
+ stdout: output,
917
+ stderr: exit_code != 0 ? output : "",
918
+ started_at: start_time,
919
+ finished_at: end_time,
920
+ duration_seconds: (end_time - start_time).to_i,
921
+ error_message: exit_code != 0 ? output : ""
922
+ }
923
+ end
924
+
925
+ private
926
+
927
+ def grpc_available?
928
+ self.class.grpc_available?
929
+ end
930
+
931
+ def execute_command_with_input(command, input)
932
+ start_time = Time.now
933
+
934
+ # Use Ruby's Open3 for proper input handling
935
+ require 'open3'
936
+
937
+ begin
938
+ stdout, stderr, status = Open3.capture3(command, stdin_data: input)
939
+ exit_code = status.exitstatus
940
+ end_time = Time.now
941
+
942
+ {
943
+ success: exit_code == 0,
944
+ exit_code: exit_code,
945
+ stdout: stdout,
946
+ stderr: stderr,
947
+ started_at: start_time,
948
+ finished_at: end_time,
949
+ duration_seconds: (end_time - start_time).to_i,
950
+ error_message: exit_code != 0 ? stderr : ""
951
+ }
952
+ rescue => e
953
+ end_time = Time.now
954
+ {
955
+ success: false,
956
+ exit_code: 1,
957
+ stdout: "",
958
+ stderr: e.message,
959
+ started_at: start_time,
960
+ finished_at: end_time,
961
+ duration_seconds: (end_time - start_time).to_i,
962
+ error_message: e.message
963
+ }
964
+ end
965
+ end
966
+
967
+ def execute_command_with_timeout(command, timeout_seconds)
968
+ # For now, just execute normally
969
+ # In a production implementation, you'd use proper timeout handling
970
+ execute_shell_command(command)
971
+ end
972
+
973
+ def find_executable(executable_name)
974
+ # Cross-platform executable finding logic
975
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
976
+ # Windows logic
977
+ # If it's already a full path, check if it exists and is executable
978
+ if executable_name.include?("\\") || executable_name.include?("/")
979
+ return executable_name if File.executable?(executable_name)
980
+ end
981
+
982
+ # Otherwise, try common extensions
983
+ ["#{executable_name}.exe", "#{executable_name}.bat", "#{executable_name}.cmd"].each do |exe|
984
+ return exe if system("#{exe} --version", out: File::NULL, err: File::NULL)
985
+ end
986
+ nil
987
+ else
988
+ # Unix logic
989
+ # If it's already a full path, check if it exists and is executable
990
+ if executable_name.include?("/")
991
+ return executable_name if File.executable?(executable_name)
992
+ end
993
+
994
+ # Otherwise, use which
995
+ `which #{executable_name}`.strip
996
+ end
997
+ end
998
+
999
+ def extract_image_id(output)
1000
+ # Extract image ID from podman pull output
1001
+ output.lines.last&.strip || ""
1002
+ end
1003
+
1004
+ def extract_layers(output)
1005
+ # Extract layer information from podman pull output
1006
+ output.lines.select { |line| line.include?("Pulling") }.map(&:strip)
1007
+ end
1008
+
1009
+ def extract_container_id(output)
1010
+ # Extract container ID from podman run output
1011
+ output.strip
1012
+ end
1013
+
1014
+ def map_container_status(status)
1015
+ case status.downcase
1016
+ when "created"
1017
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::CREATED : :created
1018
+ when "running"
1019
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::RUNNING : :running
1020
+ when "paused"
1021
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::PAUSED : :paused
1022
+ when "restarting"
1023
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::RESTARTING : :restarting
1024
+ when "removing"
1025
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::REMOVING : :removing
1026
+ when "exited"
1027
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::EXITED : :exited
1028
+ when "dead"
1029
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::DEAD : :dead
1030
+ else
1031
+ grpc_available? ? Makit::V1::Podman::Service::ContainerStatus::UNKNOWN : :unknown
1032
+ end
1033
+ end
1034
+
1035
+ def parse_time(time_string)
1036
+ return Time.now if time_string.nil?
1037
+
1038
+ # Handle integers (Unix timestamps)
1039
+ if time_string.is_a?(Integer)
1040
+ return Time.at(time_string)
1041
+ end
1042
+
1043
+ # Handle empty strings
1044
+ return Time.now if time_string.empty?
1045
+
1046
+ # Handle relative time strings like "Less than a second ago"
1047
+ if time_string.include?("ago")
1048
+ return Time.now
1049
+ end
1050
+
1051
+ begin
1052
+ Time.parse(time_string)
1053
+ rescue ArgumentError
1054
+ Time.now
1055
+ end
1056
+ end
1057
+
1058
+ def create_error_response(message)
1059
+ if grpc_available?
1060
+ Makit::V1::Podman::Service::ExecuteScriptResponse.new(
1061
+ success: false,
1062
+ error_message: message,
1063
+ exit_code: 1
1064
+ )
1065
+ else
1066
+ {
1067
+ success: false,
1068
+ error_message: message,
1069
+ exit_code: 1
1070
+ }
1071
+ end
1072
+ end
1073
+
1074
+ def convert_to_wsl_path(windows_path)
1075
+ # On Windows, Podman handles Windows paths natively
1076
+ # Just convert forward slashes to backslashes if needed
1077
+ windows_path.tr('/', '\\')
1078
+ end
1079
+ end
1080
+ end
1081
+ end