From ab31659e2bb58e7704a891e390e9d762e713074c Mon Sep 17 00:00:00 2001 From: glenux Date: Sun, 2 Jun 2024 07:06:36 +0000 Subject: [PATCH 1/5] Update .drone.yml --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index be6a72d..030c2e3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -30,7 +30,7 @@ steps: - name: publish:tag image: curlimages/curl environment: - PACKAGE_UPLOAD_URL: https://code.apps.glenux.net/api/packages/glenux/generic/docmachine-utils + PACKAGE_UPLOAD_URL: https://code.apps.glenux.net/api/packages/glenux/generic/docmachine-cli PACKAGE_BASENAME: docmachine_linux_amd64 PACKAGE_UPLOAD_TOKEN: from_secret: PACKAGE_UPLOAD_TOKEN From f55c06c05ed12c2db8cf85379966ac0f89c30c7f Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Sun, 2 Jun 2024 21:21:50 +0200 Subject: [PATCH 2/5] docs: add missing information to README --- README.md | 54 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2ffdd91..bc4cd24 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,53 @@ -# DocMachine (Utils) +# DocMachine Cli -DocMachine is a CLI tool designed to simplify the process of creating technical documentation and presentations. +DocMachine Cli is a tool designed to simplify the process of creating technical +documentation and presentations. ## Motivation This project aims to address the following challenges: -* **Automation:** Automate the generation of high-quality technical content, including documentation and presentation slides. -* **Consistency:** Ensure a consistent and polished look and feel across all content pieces. -* **Efficiency:** Reduce the time and effort required to produce content by leveraging AI tools. +* **Automation:** Automate the generation of high-quality technical content, + including documentation and presentation slides. +* **Consistency:** Ensure a consistent and polished look and feel across all + content pieces. +* **Efficiency:** Reduce the time and effort required to produce content by + leveraging AI tools. ## Features DocMachine offers a range of features to streamline the content creation process: -* **Scaffolding:** Generate a well-structured project directory with all the necessary files. -* **Building:** Compile and publish your content as HTML and PDF documents using Dockerized build processes. +* **Scaffolding:** Generate a well-structured project directory with all the + necessary files. +* **Building:** Compile and publish your content as HTML and PDF documents + using Dockerized build processes. We are actively developing the following features for future releases: -* **Planning:** Leverage LLMs (Large Language Models) to generate content outlines tailored to your specific needs and requirements. -* **Writing:** Utilize LLMs to draft content for each section and subsection, saving you valuable time and effort. +* **Planning:** Leverage LLMs (Large Language Models) to generate content + outlines tailored to your specific needs and requirements. +* **Writing:** Utilize LLMs to draft content for each section and subsection, + saving you valuable time and effort. ## Prerequisites -FIXME: list prerequisites for crystal lang & dependencies +You'll need a recent version of Crystal (>= 1.11.0) to use this project. + +You'll also need to install a few dependencies: + +* libreadline-dev +* libncurses-dev ## Getting Started -Follow these steps to start using DocMachine: +Follow these steps to start using DocMachine Cli: ### Installation ```bash -git clone https://code.apps.glenux.net/glenux/docmachine-utils.git docmachine-utils -cd docmachine-utils +git clone https://code.apps.glenux.net/glenux/docmachine-cli.git docmachine-cli +cd docmachine-cli make build make install ``` @@ -42,13 +55,14 @@ make install ### Create a New Project ```bash -docmachine scaffold my-doc-project +docmachine scaffold my-documentation-project ``` -This command will create a new directory named `my-doc-project` with the following structure: +This command will create a new directory named `my-documentation-project` with +the following structure: ``` -my-doc-project +my-documentation-project ├── _build ├── docs │ └── images # link to ../images @@ -60,7 +74,8 @@ my-doc-project ### Start Writing Content * **Documentation:** Place your Markdown files inside the `docs` directory. -* **Presentations:** Place your Markdown files (using Marp syntax) inside the `slides` directory. +* **Presentations:** Place your Markdown files (using Marp syntax) inside the + `slides` directory. * **Images:** Store your images in the respective `images` directories. ### Live-reload during writing @@ -70,6 +85,7 @@ docmachine build -a watch ``` This command will start a Docker container and build your documentation and presentations: + * **Documentation:** Built using MkDocs and served on `http://localhost:5100`. * **Presentations:** Built using Marp and served on `http://localhost:5200`. @@ -100,6 +116,6 @@ We welcome contributions to DocMachine! To contribute: ## License -DocMachine is licensed under the GPL-3.0-or-later license. See the `LICENSE` -file for details. +DocMachine Cli is licensed under the GPL-3.0-or-later license. See the +`LICENSE` file for details. From c0ae494c5761cc0e98eba0e2591037cd5cfcdf2c Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Mon, 18 Nov 2024 21:49:02 +0100 Subject: [PATCH 3/5] feat: Add support for specifying custom Docker image tag in CLI options --- src/build/cli.cr | 4 ++++ src/build/config.cr | 1 + src/build/run.cr | 53 +++++++++++++++++++++++++++++++-------------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/build/cli.cr b/src/build/cli.cr index 968db9c..529a882 100644 --- a/src/build/cli.cr +++ b/src/build/cli.cr @@ -40,6 +40,10 @@ module DocMachine::Build config.enable_tty = true end + opts.on("-i", "--image", "Use specific image:tag (default glenux/docmachine:latest)") do |image_tag| + config.image_tag = image_tag + end + commands << ->() : Nil do app = DocMachine::Build::Run.new(config) app.prepare diff --git a/src/build/config.cr b/src/build/config.cr index ec33c4d..d65fcef 100644 --- a/src/build/config.cr +++ b/src/build/config.cr @@ -7,6 +7,7 @@ module DocMachine::Build property port : Int32 = 5100 property enable_multiple : Bool = false property enable_cache : Bool = false + property image_tag : String = "glenux/docmachine:latest" def initialize(@parent : DocMachine::Config) end diff --git a/src/build/run.cr b/src/build/run.cr index 89ed74e..b5fcd3a 100644 --- a/src/build/run.cr +++ b/src/build/run.cr @@ -15,7 +15,6 @@ module DocMachine::Build data = "#{@config.data_dir}:#{@config.port}" @basehash = Digest::SHA256.hexdigest(data)[0..6] @docker_name = "docmachine-#{@basehash}" - @docker_image = "glenux/docmachine:latest" @docker_opts = [] of String @process = nil end @@ -25,7 +24,7 @@ module DocMachine::Build # setup permissions def prepare() Log.info { "basedir = #{@config.data_dir}" } - Log.info { "docker_image = #{@docker_image}" } + Log.info { "docker_image = #{@config.image_tag}" } Log.info { "action = #{@config.action}" } self._pull_image() @@ -56,13 +55,13 @@ module DocMachine::Build data_cache_file = data_cache_dir / "image.tar" Log.info { "Checking cache #{data_cache_file}..." } if ! File.exists? data_cache_file.to_s - Log.info { "Downloading #{@docker_image} image..." } - Process.run("docker", ["pull", @docker_image], output: STDOUT) + Log.info { "Downloading #{@config.image_tag} image..." } + Process.run("docker", ["pull", @config.image_tag], output: STDOUT) Log.info { "Building cache for image (#{data_cache_dir})" } FileUtils.mkdir_p(data_cache_dir) status = Process.run( "docker", - ["image", "save", @docker_image, "-o", data_cache_file.to_s], + ["image", "save", @config.image_tag, "-o", data_cache_file.to_s], output: STDOUT ) if status.success? @@ -77,7 +76,7 @@ module DocMachine::Build end if @config.enable_cache - Log.info { "Loading #{@docker_image} image from cache..." } + Log.info { "Loading #{@config.image_tag} image from cache..." } docker_image_loaded = false status = Process.run( "docker", @@ -91,16 +90,34 @@ module DocMachine::Build exit 1 end else - Log.info { "Loading #{@docker_image} image from local registry..." } + Log.info { "Loading #{@config.image_tag} image from local registry..." } # FIXME: check that local image exists end end def start() - uid = %x{id -u}.strip - gid = %x{id -g}.strip - Log.info { "uid: #{uid}" } - Log.info { "cid: #{gid}" } + # start with default uid/gid + ext_uid = %x{id -u}.strip.to_i + ext_gid = %x{id -g}.strip.to_i + + # ...but use subuid/subgid if available + File.each_line("/etc/subuid") do |line| + split = line.split(":") + next if split[0] != %x{id -u -n} + + subuid = split[1].to_i + ext_uid += subuid - 1 + end + File.each_line("/etc/subgid") do |line| + split = line.split(":") + next if split[0] != %x{id -g -n} + + subgid = split[1].to_i + ext_gid += subgid - 1 + end + + Log.info { "ext uid: #{ext_uid}" } + Log.info { "ext cid: #{ext_gid}" } docker_opts = [] of String docker_opts << "run" @@ -111,8 +128,9 @@ module DocMachine::Build docker_opts.concat ["--name", @docker_name] docker_opts << "--rm" docker_opts << "--shm-size=1gb" - docker_opts.concat ["-e", "EXT_UID=#{uid}"] - docker_opts.concat ["-e", "EXT_GID=#{gid}"] + # docker_opts << "--privileged" + docker_opts.concat ["-e", "EXT_UID=#{ext_uid}"] + docker_opts.concat ["-e", "EXT_GID=#{ext_gid}"] docker_opts.concat ["-v", "#{@config.data_dir}/docs:/app/docs"] docker_opts.concat ["-v", "#{@config.data_dir}/slides:/app/slides"] docker_opts.concat ["-v", "#{@config.data_dir}/images:/app/images"] @@ -166,11 +184,14 @@ module DocMachine::Build Log.info { "Slides: no slides directory detected." } end - docker_opts << @docker_image + docker_opts << @config.image_tag docker_opts << @config.action - Log.info { docker_opts.inspect.colorize(:yellow) } - @process = Process.new("docker", docker_opts, output: STDOUT, error: STDERR) + Log.info { + docker_str = ["docker"].concat(docker_opts).join(" ").colorize(:yellow) + "Docker: #{docker_str.to_s}" + } + @process = Process.new("docker", docker_opts, input: STDIN, output: STDOUT, error: STDERR) end def wait() From 60ab198a694f189ae710815d49b003d6d94c4564 Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Tue, 19 Nov 2024 20:40:21 +0100 Subject: [PATCH 4/5] chore: Update .gitignore to include .aider* and .env files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ea479e5..e01d78f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /bin /lib +.aider* +.env From e59ea9ff4441214810e050d6de6053d28dc84ad4 Mon Sep 17 00:00:00 2001 From: "Glenn Y. Rolland" Date: Thu, 27 Mar 2025 11:37:00 +0100 Subject: [PATCH 5/5] feat: implement and refactor container engine module This commit introduces a comprehensive implementation and refactoring of the container engine module. Key enhancements include the addition of abstract classes and the implementation of container engines. New features: - Developed `DockerEngine` and `PodmanEngine` classes, deriving from `AbstractContainerEngine` to provide modular functionality. - Added a CLI `--container-runtime` option, allowing users to select their preferred runtime, defaulting to Docker if unspecified. - Introduced a `container_runtime` property in `DocMachine::Build::Config` for improved runtime management. Refactoring: - Refactored code to replace hardcoded Docker commands with method calls to `AbstractContainerEngine`, promoting code reuse and abstraction. Signed-off-by: Glenn Y. Rolland --- src/build/cli.cr | 4 + src/build/config.cr | 2 +- src/build/run.cr | 134 ++++++++++----------- src/container/abstract_container_engine.cr | 41 +++++++ src/container/docker_engine.cr | 109 +++++++++++++++++ src/container/podman_engine.cr | 107 ++++++++++++++++ 6 files changed, 328 insertions(+), 69 deletions(-) create mode 100644 src/container/abstract_container_engine.cr create mode 100644 src/container/docker_engine.cr create mode 100644 src/container/podman_engine.cr diff --git a/src/build/cli.cr b/src/build/cli.cr index 529a882..d317bca 100644 --- a/src/build/cli.cr +++ b/src/build/cli.cr @@ -36,6 +36,10 @@ module DocMachine::Build config.enable_multiple = true end + opts.on("-r", "--container-runtime RUNTIME", "Container runtime (default docker)") do |runtime| + config.container_runtime = runtime + end + opts.on("-t", "--tty", "Enable TTY mode (needed for shell)") do config.enable_tty = true end diff --git a/src/build/config.cr b/src/build/config.cr index d65fcef..8df6b66 100644 --- a/src/build/config.cr +++ b/src/build/config.cr @@ -1,4 +1,3 @@ - module DocMachine::Build class Config property data_dir : String = Dir.current @@ -8,6 +7,7 @@ module DocMachine::Build property enable_multiple : Bool = false property enable_cache : Bool = false property image_tag : String = "glenux/docmachine:latest" + property container_runtime : String = "docker" def initialize(@parent : DocMachine::Config) end diff --git a/src/build/run.cr b/src/build/run.cr index b5fcd3a..10f11b5 100644 --- a/src/build/run.cr +++ b/src/build/run.cr @@ -1,4 +1,3 @@ - require "path" require "file_utils" require "socket" @@ -6,25 +5,46 @@ require "socket" require "./module" require "./config" require "../common/network" +require "../container/abstract_container_engine" +require "../container/docker_engine" +require "../container/podman_engine" module DocMachine::Build class Run Log = DocMachine::Build::Log.for("run") + # Instance variable for the container engine + property container_engine : DocMachine::Container::AbstractContainerEngine + + @basehash : String + @docker_name : String + @docker_opts : Array(String) + @process : Process? + def initialize(@config : DocMachine::Build::Config) data = "#{@config.data_dir}:#{@config.port}" @basehash = Digest::SHA256.hexdigest(data)[0..6] @docker_name = "docmachine-#{@basehash}" @docker_opts = [] of String @process = nil + + # Initialize the container engine based on configuration + @container_engine = ( + if @config.container_runtime == "podman" + DocMachine::Container::PodmanEngine.new + else + DocMachine::Container::DockerEngine.new + end + ) end - # cleanup environment - # create directories - # setup permissions + # Cleanup environment + # Create directories + # Setup permissions def prepare() Log.info { "basedir = #{@config.data_dir}" } - Log.info { "docker_image = #{@config.image_tag}" } + Log.info { "container_image = #{@config.image_tag}" } + Log.info { "container_runtime = #{@config.container_runtime}" } Log.info { "action = #{@config.action}" } self._pull_image() @@ -33,62 +53,41 @@ module DocMachine::Build private def _avoid_duplicates Log.info { "Multiple Instances: stopping duplicate containers (for #{@docker_name})" } - docker_cid = %x{docker ps -f "name=#{@docker_name}" -q}.strip + container_id = @container_engine.find_container_id(@docker_name) - Log.info { "Multiple Instances: docker_name: #{@docker_name}" } - Log.info { "Multiple Instances: docker_cid: #{docker_cid || "-"}" } + Log.info { "Multiple Instances: container_name: #{@docker_name}" } + Log.info { "Multiple Instances: container_id: #{container_id || "-"}" } - if !docker_cid.empty? - Process.run("docker", ["kill", @docker_name]) - Process.run("docker", ["rm", @docker_name]) + if !container_id.empty? + @container_engine.kill_container(@docker_name) + @container_engine.remove_container(@docker_name) end end def _pull_image # FIXME: add option to force update - data_cache_dir = if ENV["XDG_CACHE_HOME"]? + data_cache_dir = if ENV["XDG_CACHE_HOME"]? Path[ENV["XDG_CACHE_HOME"], "docmachine"] - else Path[ENV["HOME"], ".cache", "docmachine"] + else + Path[ENV["HOME"], ".cache", "docmachine"] end - ## Build cache if it doesnt exist - data_cache_file = data_cache_dir / "image.tar" + ## Build cache if it doesn't exist + data_cache_file = data_cache_dir / "image.tar" Log.info { "Checking cache #{data_cache_file}..." } - if ! File.exists? data_cache_file.to_s + if !File.exists? data_cache_file.to_s Log.info { "Downloading #{@config.image_tag} image..." } - Process.run("docker", ["pull", @config.image_tag], output: STDOUT) + @container_engine.pull_image(@config.image_tag) Log.info { "Building cache for image (#{data_cache_dir})" } FileUtils.mkdir_p(data_cache_dir) - status = Process.run( - "docker", - ["image", "save", @config.image_tag, "-o", data_cache_file.to_s], - output: STDOUT - ) - if status.success? - Log.info { "done" } - else - Log.error { "Unable to save cache image" } - exit 1 - end - - else - Log.info { "Cache already exist. Skipping." } + status = @container_engine.save_image(@config.image_tag, data_cache_file.to_s) + else + Log.info { "Cache already exists. Skipping." } end if @config.enable_cache Log.info { "Loading #{@config.image_tag} image from cache..." } - docker_image_loaded = false - status = Process.run( - "docker", - ["image", "load", "-i", data_cache_file.to_s], - output: STDOUT - ) - if status.success? - Log.info { "done" } - else - Log.error { "Unable to load cache image" } - exit 1 - end + status = @container_engine.load_image(data_cache_file.to_s) else Log.info { "Loading #{@config.image_tag} image from local registry..." } # FIXME: check that local image exists @@ -96,35 +95,34 @@ module DocMachine::Build end def start() - # start with default uid/gid + # Start with default uid/gid ext_uid = %x{id -u}.strip.to_i ext_gid = %x{id -g}.strip.to_i # ...but use subuid/subgid if available File.each_line("/etc/subuid") do |line| split = line.split(":") - next if split[0] != %x{id -u -n} + next if split[0] != %x{id -u -n}.strip - subuid = split[1].to_i + subuid = split[1].to_i ext_uid += subuid - 1 end File.each_line("/etc/subgid") do |line| split = line.split(":") - next if split[0] != %x{id -g -n} + next if split[0] != %x{id -g -n}.strip subgid = split[1].to_i ext_gid += subgid - 1 end Log.info { "ext uid: #{ext_uid}" } - Log.info { "ext cid: #{ext_gid}" } + Log.info { "ext gid: #{ext_gid}" } docker_opts = [] of String - docker_opts << "run" docker_opts << "-i" - # add tty support + # Add tty support docker_opts << "-t" if @config.enable_tty - # add container name + # Add container name docker_opts.concat ["--name", @docker_name] docker_opts << "--rm" docker_opts << "--shm-size=1gb" @@ -143,7 +141,7 @@ module DocMachine::Build Log.info { "Theme: detected Marp files. Adding option to command line (#{docker_opt_marp_theme})" } else Log.info { "Theme: no theme detected. Using default files" } - end + end ## Detect Mkdocs configuration - old format (full) if File.exists?("#{@config.data_dir}/mkdocs.yml") @@ -158,7 +156,7 @@ module DocMachine::Build Log.info { "Docs: detected mkdocs-patch.yml file. Adding option to command line (#{docker_opt_mkdocs_config})" } else Log.info { "Docs: no mkdocs-patch.yml detected. Using default files" } - end + end ## Detect docs if Dir.exists?("#{@config.data_dir}/docs") @@ -175,23 +173,30 @@ module DocMachine::Build ## Detect slides if Dir.exists?("#{@config.data_dir}/slides") Log.info { "Slides: detected slides directory." } - marp_port = Network.find_port(@config.port+100) + marp_port = Network.find_port(@config.port + 100) docker_opt_marp_port = ["-p", "#{marp_port}:5200"] - docker_opts.concat docker_opt_marp_port + docker_opts.concat docker_opt_marp_port Log.info { "Slides: Adding option to command line (#{docker_opt_marp_port})" } Log.notice { "Slides: Using port #{marp_port} for slides" } else Log.info { "Slides: no slides directory detected." } - end + end docker_opts << @config.image_tag docker_opts << @config.action - Log.info { - docker_str = ["docker"].concat(docker_opts).join(" ").colorize(:yellow) - "Docker: #{docker_str.to_s}" + Log.info { + docker_str = [@config.container_runtime].concat(docker_opts).join(" ").colorize(:yellow) + "#{@config.container_runtime.capitalize}: #{docker_str}" } - @process = Process.new("docker", docker_opts, input: STDIN, output: STDOUT, error: STDERR) + + @process = @container_engine.run_container( + @config.image_tag, + @config.action, + @docker_name, + docker_opts, + @config.enable_tty + ) end def wait() @@ -200,16 +205,9 @@ module DocMachine::Build Signal::INT.trap do Log.warn { "Received CTRL-C" } - process.signal(Signal::KILL) - Process.run("docker", ["kill", @docker_name]) + @container_engine.kill_container(@docker_name) end process.wait end - - def stop() - end - - def docker_opts() - end end end diff --git a/src/container/abstract_container_engine.cr b/src/container/abstract_container_engine.cr new file mode 100644 index 0000000..6341a2c --- /dev/null +++ b/src/container/abstract_container_engine.cr @@ -0,0 +1,41 @@ +module DocMachine::Container + + class ContainerError < Exception ; end + + class SaveError < ContainerError ; end + class KillError < ContainerError ; end + class RemoveError < ContainerError ; end + class LoadError < ContainerError ; end + class RunError < ContainerError ; end + class PullError < ContainerError ; end + + abstract class AbstractContainerEngine + # Pulls the specified Docker/Podman image + abstract def pull_image(image_tag : String) : Nil + + # Loads the Docker/Podman image from a local cache + abstract def load_image(cache_path : String) : Nil + + # Saves the Docker/Podman image to a local cache + abstract def save_image(image_tag : String, cache_path : String) : Nil + + # Runs a container with the given options + abstract def run_container( + image_tag : String, + action : String, + docker_name : String, + docker_opts : Array(String), + enable_tty : Bool + ) : Process + + # Kills a running container by name + abstract def kill_container(container_name : String) : Nil + + # Removes a container by name + abstract def remove_container(container_name : String) : Nil + + # Finds the container ID based on its name + abstract def find_container_id(name : String) : String + end + +end diff --git a/src/container/docker_engine.cr b/src/container/docker_engine.cr new file mode 100644 index 0000000..1b82fbb --- /dev/null +++ b/src/container/docker_engine.cr @@ -0,0 +1,109 @@ +module DocMachine::Container + + class DockerEngine < AbstractContainerEngine + # Pulls the specified Docker image + def pull_image(image_tag : String) : Nil + Log.info { "Pulling Docker image: #{image_tag}" } + status = Process.run("docker", ["pull", image_tag], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully pulled Docker image: #{image_tag}" } + true + else + Log.error { "Failed to pull Docker image: #{image_tag}" } + false + end + end + + # Loads the Docker image from a local cache + def load_image(cache_path : String) : Nil + Log.info { "Loading Docker image from cache: #{cache_path}" } + status = Process.run("docker", ["image", "load", "-i", cache_path], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully loaded Docker image from cache." } + true + else + Log.error { "Failed to load Docker image from cache." } + false + end + end + + # Saves the Docker image to a local cache + def save_image(image_tag : String, cache_path : String) : Nil + Log.info { "Saving Docker image #{image_tag} to cache at #{cache_path}" } + status = Process.run("docker", ["image", "save", image_tag, "-o", cache_path], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully saved Docker image to cache." } + true + else + Log.error { "Failed to save Docker image to cache." } + false + end + end + + # Runs a Docker container with the given options + def run_container( + image_tag : String, + action : String, + docker_name : String, + docker_opts : Array(String), + enable_tty : Bool + ) : Process + Log.info { "Running Docker container: #{docker_name}" } + + # Construct the full Docker run command + cmd = ["docker", "run"] + docker_opts + [image_tag, action] + + # Log the command for debugging + Log.debug { "Docker run command: #{cmd.join(" ")}" } + + # Start the Docker container process + process = Process.new("docker", ["run"] + docker_opts + [image_tag, action], input: STDIN, output: STDOUT, error: STDERR) + + Log.info { "Docker container #{docker_name} started." } + + process + end + + # Kills a running Docker container by name + def kill_container(container_name : String) : Nil + Log.info { "Killing Docker container: #{container_name}" } + status = Process.run("docker", ["kill", container_name], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully killed Docker container: #{container_name}" } + true + else + Log.error { "Failed to kill Docker container: #{container_name}" } + false + end + end + + # Removes a Docker container by name + def remove_container(container_name : String) : Nil + Log.info { "Removing Docker container: #{container_name}" } + status = Process.run("docker", ["rm", container_name], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully removed Docker container: #{container_name}" } + true + else + Log.error { "Failed to remove Docker container: #{container_name}" } + false + end + end + + # Finds the container ID based on its name + def find_container_id(name : String) : String + Log.info { "Finding Docker container ID for name: #{name}" } + output = IO::Memory.new + status = Process.run("docker", ["ps", "-f", "name=#{name}", "-q"], output: output, error: STDERR) + if status.success? + container_id = output.to_s.strip + Log.info { "Found Docker container ID: #{container_id}" } + container_id + else + Log.error { "Failed to find Docker container ID for name: #{name}" } + "" + end + end + end + +end diff --git a/src/container/podman_engine.cr b/src/container/podman_engine.cr new file mode 100644 index 0000000..0b02904 --- /dev/null +++ b/src/container/podman_engine.cr @@ -0,0 +1,107 @@ +module DocMachine::Container + + class PodmanEngine < AbstractContainerEngine + # Pulls the specified Podman image + def pull_image(image_tag : String) : Nil + Log.info { "Pulling Podman image: #{image_tag}" } + status = Process.run("podman", ["pull", image_tag], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully pulled Podman image: #{image_tag}" } + true + else + Log.error { "Failed to pull Podman image: #{image_tag}" } + false + end + end + + # Loads the Podman image from a local cache + def load_image(cache_path : String) : Nil + Log.info { "Loading Podman image from cache: #{cache_path}" } + status = Process.run("podman", ["image", "load", "-i", cache_path], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully loaded Podman image from cache." } + else + Log.error { "Failed to load Podman image from cache." } + raise LoadError.new("Failed to load Podman image from cache.") + end + end + + # Saves the Podman image to a local cache + def save_image(image_tag : String, cache_path : String) : Nil + Log.info { "Saving Podman image #{image_tag} to cache at #{cache_path}" } + status = Process.run("podman", ["image", "save", image_tag, "-o", cache_path], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully saved Podman image to cache." } + else + Log.error { "Failed to save Podman image to cache." } + raise SaveError.new("Failed to save Podman image to cache.") + end + end + + # Runs a Podman container with the given options + def run_container( + image_tag : String, + action : String, + docker_name : String, + docker_opts : Array(String), + enable_tty : Bool + ) : Process + Log.info { "Running Podman container: #{docker_name}" } + + # Construct the full Podman run command + cmd = ["podman", "run"] + docker_opts + [image_tag, action] + + # Log the command for debugging + Log.debug { "Podman run command: #{cmd.join(" ")}" } + + # Start the Podman container process + process = Process.new("podman", ["run"] + docker_opts + [image_tag, action], input: STDIN, output: STDOUT, error: STDERR) + + Log.info { "Podman container #{docker_name} started." } + + process + end + + # Kills a running Podman container by name + def kill_container(container_name : String) : Nil + Log.info { "Killing Podman container: #{container_name}" } + status = Process.run("podman", ["kill", container_name], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully killed Podman container: #{container_name}" } + true + else + Log.error { "Failed to kill Podman container: #{container_name}" } + false + end + end + + # Removes a Podman container by name + def remove_container(container_name : String) : Nil + Log.info { "Removing Podman container: #{container_name}" } + status = Process.run("podman", ["rm", container_name], output: STDOUT, error: STDERR) + if status.success? + Log.info { "Successfully removed Podman container: #{container_name}" } + true + else + Log.error { "Failed to remove Podman container: #{container_name}" } + false + end + end + + # Finds the container ID based on its name + def find_container_id(name : String) : String + Log.info { "Finding Podman container ID for name: #{name}" } + output = IO::Memory.new + status = Process.run("podman", ["ps", "-f", "name=#{name}", "-q"], output: output, error: STDERR) + if status.success? + container_id = output.to_s.strip + Log.info { "Found Podman container ID: #{container_id}" } + container_id + else + Log.error { "Failed to find Podman container ID for name: #{name}" } + "" + end + end + end + +end