diff --git a/shard.yml b/shard.yml index e4b04ab..961aabd 100644 --- a/shard.yml +++ b/shard.yml @@ -21,5 +21,7 @@ dependencies: targets: dnsmanager: main: src/main.cr + dnsmanager-client: + main: src/client/main.cr license: MIT diff --git a/src/client/main.cr b/src/client/main.cr new file mode 100644 index 0000000..be31721 --- /dev/null +++ b/src/client/main.cr @@ -0,0 +1,142 @@ +require "authd" +require "ipc" +require "../network.cr" +require "../storage.cr" +require "yaml" + +require "baguette-crystal-base" + +require "../config" + +require "./lib/*" + +class Context + class_property command = "not-implemented" + class_property args : Array(String)? = nil +end + +require "./parser.cr" + +#def read_searches +# Array(DNSManager::Request::BLAH).from_json_files Context.args.not_nil! +#end + +class Actions + property the_call = {} of String => Proc(Nil) + property dnsmanagerd : DNSManager::Client + property authd : AuthD::Client + property authd_config : Baguette::Configuration::Auth + property config : Baguette::Configuration::DNSManager + + def initialize(@dnsmanagerd, @authd, @authd_config, @config) + # + # Admin section. + # + + # Maintainance + @the_call["admin-maintainance"] = ->admin_maintainance + end + + def admin_maintainance + subjects = Context.args.not_nil! + past_is_verbosity = false + subjects.each do |subject| + begin + case subject + when /verbosity/i + Baguette::Log.info "changing verbosity" + past_is_verbosity = true + next + end + + key = @authd_config.shared_key + + if past_is_verbosity + sub = DNSManager::Request::Maintainance::Subject::Verbosity + value = subject.to_i + pp! sub, value + pp! @dnsmanagerd.admin_maintainance key, sub, value + else + sub = DNSManager::Request::Maintainance::Subject.parse(subject) + pp! sub + pp! @dnsmanagerd.admin_maintainance key, sub + end + rescue e + puts "error for admin_maintainance #{subject}: #{e.message}" + end + end + end +end + +def main + + simulation, no_configuration, configuration_file = Baguette::Configuration.option_parser + + # Authd configuration. + authd_config = if no_configuration + Baguette::Log.info "do not load a configuration file." + Baguette::Configuration::Auth.new + else + # Configuration file is for dnsmanagerd. + Baguette::Configuration::Auth.get || Baguette::Configuration::Auth.new + end + if key_file = authd_config.shared_key_file + authd_config.shared_key = File.read(key_file).chomp + end + + # Authd configuration. + config = if no_configuration + Baguette::Log.info "do not load a configuration file." + Baguette::Configuration::DNSManager.new + else + # Configuration file is for dnsmanagerd. + Baguette::Configuration::DNSManager.get || Baguette::Configuration::DNSManager.new + end + + Baguette::Context.verbosity = config.verbosity + Baguette::Log.info "verbosity: #{config.verbosity}." + + parsing_cli authd_config + + # dnsmanagerd connection and authentication + dnsmanagerd = DNSManager::Client.new + + if simulation + pp! authd_config + pp! Context.command + pp! Context.args + exit 0 + end + + # Authd authentication, get the token and quit right away. + # If login == pass == "undef": do not even try. + if authd_config.login.nil? || authd_config.pass.nil? + Baguette::Log.info "no authd login" + else + login = authd_config.login.not_nil! + pass = authd_config.pass.not_nil! + token = authd_get_token login: login, pass: pass + dnsmanagerd.login token + end + + authd = AuthD::Client.new + actions = Actions.new dnsmanagerd, authd, authd_config, config + + # Now we did read the intent, we should proceed doing what was asked. + begin + actions.the_call[Context.command].call + rescue e + Baguette::Log.info "The command is not recognized (or implemented)." + end + + # dnsmanagerd disconnection + dnsmanagerd.close + authd.close +rescue e + Baguette::Log.info "Exception: #{e}" +end + +# Command line: +# tool [options] command subcommand [options-for-subcommand] [YAML-or-JSON-files] + +main diff --git a/src/client/parser.cr b/src/client/parser.cr new file mode 100644 index 0000000..4d9b5e5 --- /dev/null +++ b/src/client/parser.cr @@ -0,0 +1,115 @@ +require "option_parser" + + +class OptionParser + def to_s(io : IO) + if banner = @banner + io << banner + io << "\n\n" + end + @flags.join io, "\n" + end +end + + +def parsing_cli(authd_config : Baguette::Configuration::Auth) + + opt_authd_admin = -> (parser : OptionParser, authd_config : Baguette::Configuration::Auth) { + parser.on "-k file", "--key-file file", "Read the authd shared key from a file." do |file| + authd_config.shared_key = File.read(file).chomp + Baguette::Log.info "Key for admin operations: #{authd_config.shared_key}." + end + } + + # frequently used functions + opt_authd_login = -> (parser : OptionParser, authd_config : Baguette::Configuration::Auth) { + parser.on "-l LOGIN", "--login LOGIN", "Authd user login." do |login| + authd_config.login = login + Baguette::Log.info "User login for authd: #{authd_config.login}." + end + parser.on "-p PASSWORD", "--password PASSWORD", "Authd user password." do |password| + authd_config.pass = password + Baguette::Log.info "User password for authd: #{authd_config.pass}." + end + } + + opt_help = -> (parser : OptionParser) { + parser.on "-h", "--help", "Prints command usage." do + puts "usage: #{PROGRAM_NAME} command -h" + puts + puts parser + + case Context.command + when /admin-maintainance/ + Baguette::Log.warning "should provide subjects to request" + Baguette::Log.warning "as in:" + DNSManager::Request::Maintainance::Subject.names.each do |n| + Baguette::Log.warning "- #{n}" + end + end + + exit 0 + end + } + + # Unrecognized parameters are used to create commands with multiple arguments. + # Example: user add _login email phone_ + # Here, login, email and phone are unrecognized arguments. + # Still, the "user add" command expect them. + unrecognized_args_to_context_args = -> (parser : OptionParser, + nexact : Int32?, + at_least : Int32?) { + + # With the right args, these will be interpreted as serialized data. + parser.unknown_args do |args| + + # either we test with the exact expected number of arguments or the least. + if exact = nexact + if args.size != exact + Baguette::Log.error "#{parser}" + exit 1 + end + elsif least = at_least + if args.size < least + Baguette::Log.error "#{parser}" + exit 1 + end + else + Baguette::Log.error "Number of parameters not even provided!" + Baguette::Log.error "#{parser}" + exit 1 + end + + args.each do |arg| + Baguette::Log.debug "Unrecognized argument: #{arg} (adding to Context.args)" + if Context.args.nil? + Context.args = Array(String).new + end + Context.args.not_nil! << arg + end + end + } + + parser = OptionParser.new do |parser| + parser.banner = "Welcome on the DNSManager CLI administration." + + # Admin section. + parser.on "admin", "Admin operations." do + # All admin operations require the shared key. + opt_authd_admin.call parser, authd_config + + # Maintenance. + parser.on("maintainance", "Maintainance operation of the website.") do + Baguette::Log.info "Maintainance operation of the website." + Context.command = "admin-maintainance" + parser.banner = "COMMAND: admin maintainance subject [value]" + unrecognized_args_to_context_args.call parser, nil, 1 + end + + end + + opt_help.call parser + end + + parser.parse +end diff --git a/src/config.cr b/src/config.cr new file mode 100644 index 0000000..6a77d6e --- /dev/null +++ b/src/config.cr @@ -0,0 +1,11 @@ + +class Baguette::Configuration + class DNSManager < IPC + property service_name : String = "dnsmanager" + property recreate_indexes : Bool = false + property storage_directory : String = "storage" + + def initialize + end + end +end diff --git a/src/main.cr b/src/main.cr index c82404b..be446c8 100644 --- a/src/main.cr +++ b/src/main.cr @@ -6,16 +6,7 @@ require "ipc/json" require "authd" require "baguette-crystal-base" -class Baguette::Configuration - class DNSManager < IPC - property service_name : String = "dnsmanager" - property recreate_indexes : Bool = false - property storage_directory : String = "storage" - - def initialize - end - end -end +require "./config" module DNSManager class Exception < ::Exception