require "crinja" require "passwd" require "./service/service.cr" require "./config.cr" def sanitize_path(path) path.gsub /\/\/+/, "/" end class Service alias CrinjaHash = Hash(String, Hash(String, Int32) | String | Crinja::Callable::Instance | Int32 | Nil) def to_genconfig CrinjaHash.new.tap do |entry| entry["name"] = name entry["id"] = full_id entry["environment"] = environment.name entry["root"] = root entry["domain"] = domain entry["ports"] = ports if !non_runnable user = Passwd.new("/etc/passwd", "/etc/group") .try &.get_user(user_name).not_nil! entry["uid"] = user.uid entry["gid"] = user.gid entry["user"] = user.login end entry["consumers"] = Crinja.function do token = arguments.varargs[0].to_s get_consumers(token).map &.id end entry["provider"] = Crinja.function do token = arguments.varargs[0].to_s providers.find(&.[0].==(token)).try &.[1] end end end end module GenConfig # FIXME: The fuck has this become. alias Variables = String | Array(String) | Array(Variables) | Hash(String, String) | Service::CrinjaHash | Array(Service::CrinjaHash) | Hash(String, Array(Service::CrinjaHash)) | Hash(String, Service::CrinjaHash) | Crinja::Callable::Instance def self.parse_options(unparsed : Array(String)) options = ARGV.map(&.split '=') hash = Hash(String, Variables).new options.each do |entry| key, value = entry old_value = hash[key]? case old_value when nil hash[key] = value when String hash[key] = [old_value, value] when Array(String) old_value << value end end hash end end class GenConfig::Exception < Exception end class GenConfig::Context getter root : String def initialize(@root) end def generate(template, target : String, options : Hash(String, Variables)) context = Service::Context.new context.load target_file = File.open target, "w" sources = [ "#{SYSTEM_CONFIGURATION_DIRECTORY}/templates", "#{SHARED_DATA_DIRECTORY}/templates" ] sources = sources .map(&.+("/#{template}.j2")) .map { |x| sanitize_path x } source = sources.find do |source| if File.exists? source puts "Generating '#{target}' from '#{source}'" next true end end unless source raise Exception.new "Could not find template to generate file ('#{target}')." end if service_id = ENV["SERVICE_ID"]? if service = context.get_service_by_id service_id environment = service.environment options["service"] = service.to_genconfig options["providers"] = service.providers.compact_map do |token, provider| provider = context.get_service_by_id provider next unless provider {token, provider.to_genconfig} end.to_h options["consumers"] = service.provides .map(&.token) .map{ |token| {token, service.get_consumers(token).map &.to_genconfig} } .to_h end end options["get_service"] = Crinja.function do context.get_service_by_id(arguments.varargs[0].to_s).try &.to_genconfig end options["raise"] = Crinja.function do message = arguments.varargs.join "\n" raise Service::Exception.new "template error: #{message}" end # FIXME: Move this to a separate binary? options["random_password"] = Crinja.function do id = (arguments.varargs[0]? || options["id"]).to_s password_id = arguments.varargs[1]? || "main" _service = context.get_service_by_id(id).not_nil! # FIXME: hardcoded path password_file = "#{_service.root}/password_#{password_id}" if File.exists? password_file File.read password_file else password = `dd if=/dev/urandom bs=64 count=1 | base64 -` password = password.gsub('\n', "") File.write password_file, password password end end template = File.read source target_file << Crinja.render(template, options) target_file.close end end template = ARGV[0]? target = ARGV[1]? if target.nil? puts "usage: configure [option1=value1 [option2=value2 […]]]" exit 1 end ARGV.shift ARGV.shift options = GenConfig.parse_options ARGV begin GenConfig::Context.new("/").generate(template, target, options) rescue e : GenConfig::Exception STDERR.puts "Fatal error: #{e.message}" exit 1 rescue e : Crinja::TypeError STDERR.puts "Error reading template: #{e.message}" exit 1 rescue e STDERR.puts "unhandled exception: #{e.message}" e.backtrace.map do |line| STDERR << " - " << line << '\n' end end