docmachine-cli/src/build/run.cr

216 lines
7.1 KiB
Crystal
Raw Normal View History

2023-04-24 18:43:04 +02:00
require "path"
require "file_utils"
2024-02-14 14:51:15 +01:00
require "socket"
2023-04-24 18:43:04 +02:00
2023-04-25 12:28:16 +02:00
require "./module"
require "./config"
require "../common/network"
module DocMachine::Build
class Run
2023-04-25 12:28:16 +02:00
Log = DocMachine::Build::Log.for("run")
2023-04-24 18:43:04 +02:00
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
end
# cleanup environment
# create directories
# setup permissions
def prepare()
2023-04-25 12:28:16 +02:00
Log.info { "basedir = #{@config.data_dir}" }
Log.info { "docker_image = #{@config.image_tag}" }
2023-04-25 12:28:16 +02:00
Log.info { "action = #{@config.action}" }
2023-04-24 18:43:04 +02:00
self._pull_image()
self._avoid_duplicates() unless @config.enable_multiple
2023-04-24 18:43:04 +02:00
end
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
Log.info { "Multiple Instances: docker_name: #{@docker_name}" }
Log.info { "Multiple Instances: docker_cid: #{docker_cid || "-"}" }
if !docker_cid.empty?
Process.run("docker", ["kill", @docker_name])
Process.run("docker", ["rm", @docker_name])
end
end
2023-04-24 18:43:04 +02:00
def _pull_image
2023-04-25 12:28:16 +02:00
# FIXME: add option to force update
data_cache_dir = if ENV["XDG_CACHE_HOME"]?
Path[ENV["XDG_CACHE_HOME"], "docmachine"]
else Path[ENV["HOME"], ".cache", "docmachine"]
end
2023-04-24 18:43:04 +02:00
## Build cache if it doesnt exist
2023-04-24 18:43:04 +02:00
data_cache_file = data_cache_dir / "image.tar"
2023-04-25 12:28:16 +02:00
Log.info { "Checking cache #{data_cache_file}..." }
2023-04-24 18:43:04 +02:00
if ! File.exists? data_cache_file.to_s
Log.info { "Downloading #{@config.image_tag} image..." }
Process.run("docker", ["pull", @config.image_tag], output: STDOUT)
2023-04-25 12:28:16 +02:00
Log.info { "Building cache for image (#{data_cache_dir})" }
2023-04-24 18:43:04 +02:00
FileUtils.mkdir_p(data_cache_dir)
2023-04-27 09:50:11 +02:00
status = Process.run(
2023-04-25 12:28:16 +02:00
"docker",
["image", "save", @config.image_tag, "-o", data_cache_file.to_s],
2023-04-25 12:28:16 +02:00
output: STDOUT
)
2023-04-27 09:50:11 +02:00
if status.success?
Log.info { "done" }
else
Log.error { "Unable to save cache image" }
exit 1
end
2023-04-24 18:43:04 +02:00
else
2023-04-25 12:28:16 +02:00
Log.info { "Cache already exist. Skipping." }
2023-04-24 18:43:04 +02:00
end
2023-04-25 12:28:16 +02:00
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
2023-04-27 09:50:11 +02:00
else
Log.info { "Loading #{@config.image_tag} image from local registry..." }
# FIXME: check that local image exists
2023-04-27 09:50:11 +02:00
end
2023-04-24 18:43:04 +02:00
end
def start()
# 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"
docker_opts << "-i"
# add tty support
docker_opts << "-t" if @config.enable_tty
# add container name
docker_opts.concat ["--name", @docker_name]
docker_opts << "--rm"
docker_opts << "--shm-size=1gb"
# 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"]
docker_opts.concat ["-v", "#{@config.data_dir}/_build:/app/_build"]
## Detect Marp SCSS
if File.exists?("#{@config.data_dir}/.marp/theme.scss")
docker_opt_marp_theme = ["-v", "#{@config.data_dir}/.marp:/app/.marp"]
docker_opts.concat docker_opt_marp_theme
2023-04-25 12:28:16 +02:00
Log.info { "Theme: detected Marp files. Adding option to command line (#{docker_opt_marp_theme})" }
else
2023-04-25 12:28:16 +02:00
Log.info { "Theme: no theme detected. Using default files" }
end
## Detect Mkdocs configuration - old format (full)
if File.exists?("#{@config.data_dir}/mkdocs.yml")
Log.info { "Docs: detected mkdocs.yml file. Please rename to mkdocs-patch.yml" }
exit 1
end
## Detect Mkdocs configuration - new format (patch)
if File.exists?("#{@config.data_dir}/mkdocs-patch.yml")
docker_opt_mkdocs_config = ["-v", "#{@config.data_dir}/mkdocs-patch.yml:/app/mkdocs-patch.yml"]
docker_opts.concat docker_opt_mkdocs_config
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
## Detect docs
if Dir.exists?("#{@config.data_dir}/docs")
Log.info { "Docs: detected docs directory." }
mkdocs_port = Network.find_port(@config.port)
docker_opt_mkdocs_port = ["-p", "#{mkdocs_port}:5100"]
docker_opts.concat docker_opt_mkdocs_port
Log.notice { "Using port #{mkdocs_port} for docs" }
Log.info { "Docs: Adding option to command line (#{docker_opt_mkdocs_port})" }
else
Log.info { "Docs: no docs detected." }
end
## Detect slides
if Dir.exists?("#{@config.data_dir}/slides")
Log.info { "Slides: detected slides directory." }
marp_port = Network.find_port(@config.port+100)
docker_opt_marp_port = ["-p", "#{marp_port}:5200"]
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
2023-04-25 12:28:16 +02:00
Log.info { "Slides: no slides directory detected." }
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}"
}
@process = Process.new("docker", docker_opts, input: STDIN, output: STDOUT, error: STDERR)
end
def wait()
process = @process
return if process.nil?
Signal::INT.trap do
2023-04-25 12:28:16 +02:00
Log.warn { "Received CTRL-C" }
process.signal(Signal::KILL)
Process.run("docker", ["kill", @docker_name])
end
process.wait
end
def stop()
end
def docker_opts()
end
end
end