From 1bae8939d881984e0f45df63d3dd734033323e66 Mon Sep 17 00:00:00 2001 From: Vasco Santos <17835679+valexsantos@users.noreply.github.com> Date: Sat, 17 Feb 2024 09:31:56 +0000 Subject: [PATCH] Client configuration support (#8) # Summary MKIt client config support # Description On `mkitc` first call, default configuration will be copied to `$HOME/.mkit` with `local`default profile set. You can add more servers and change active profile with `$mkitc profile set `, e.g. `$mkitc profile set server_2` ``` # ~/.mkit/mkitc_config.yml mkit: local: server.uri: http://localhost:4567 server_2: # you can add more servers. change the client active profile with mkitc profile command server.uri: http://192.168.29.232:4567 ``` # Usage ``` $ mkitc help profile Usage: mkitc profile <[set ]|[show]> mkit client configuration profile Options: set set mkit client configuration profile show show mkit client current profile ``` --------- Co-authored-by: Vasco Santos <7835679+valexsantos@users.noreply.github.com> --- README.md | 56 +++++++- bin/mkitc | 301 +++++++++++++++++++++++++++------------- config/mkitc_config.yml | 3 + config/mkitd_config.sh | 2 +- lib/mkit/version.rb | 2 +- 5 files changed, 264 insertions(+), 100 deletions(-) create mode 100644 config/mkitc_config.yml diff --git a/README.md b/README.md index c7ab673..4294eb5 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,13 @@ or after the `gem install mkit-.gem`. The server and client will be ins 0.65s info: MKIt is up and running! [ec=0xbe0] [pid=45804] [2023-12-29 15:46:04 +0000] ``` -There's also samples on the samples dir, for daemontools and systemd. +There's also samples on the samples dir, for `daemontools` and `systemd`. ### Accessing the API -A client is provided to interact with mkit server. +A client is provided to interact with `mkit server`. -Run `mkitc help` to list current supported commands. +Run `mkitc help` for a list of current supported commands. ``` Usage: mkitc [options] @@ -75,6 +75,7 @@ update update service rm remove service version prints mkit server version proxy haproxy status and control +profile mkit client configuration profile Run 'mkitc help ' for specific command information. ``` @@ -93,7 +94,40 @@ The service `postgres` is available on IP `10.210.198.10:5432` ## Configuration -On startup, configuration files on `config` directory will be copied to `/etc/mkit`. HAProxy config directory and control commands are defined on `mkit_config.yml` +### Server configuration + +On startup, configuration files on `config` directory will be copied to `/etc/mkit`. + +The server is available by default on `http://localhost:4567` but you can configure server startup parameters on `/etc/mkit/mkitd_config.sh` + +Please check `samples/systemd` or `samples/daemontools` directories for more details. + +``` +# /etc/mkit/mkitd_config.sh +# +# mkitd server options (for systemd unit | daemontools) +# +OPTIONS="" +# e.g. OPTIONS="-b 0.0.0.0" +``` +HAProxy config directory and control commands are defined on `mkit_config.yml` + +``` +# /etc/mkit/mkit_config.yml - mkit server configuration file. +mkit: + my_network: + ip: 10.210.198.1 + haproxy: + config_dir: /etc/haproxy/haproxy.d + ctrl: + start: systemctl start haproxy + stop: systemctl stop haproxy + reload: systemctl reload haproxy + restart: systemctl restart haproxy + status: systemctl status haproxy + database: + env: development +``` You must configure `haproxy` to use config directory. e.g. on Ubuntu @@ -111,6 +145,20 @@ CONFIG="/etc/haproxy/haproxy.d" # Add extra flags here, see haproxy(1) for a few options #EXTRAOPTS="-de -m 16" ``` +### Client configuration + +On `mkitc` first call, default configuration will be copied to `$HOME/.mkit` with `local`default profile set. + +You can add more servers and change active profile with `$mkitc profile set `, e.g. `$mkitc profile set server_2` + +``` +# ~/.mkit/mkitc_config.yml +mkit: + local: + server.uri: http://localhost:4567 + server_2: # you can add more servers. change the client active profile with mkitc profile command + server.uri: http://192.168.29.232:4567 +``` ### Service diff --git a/bin/mkitc b/bin/mkitc index 684049d..30b3d2d 100755 --- a/bin/mkitc +++ b/bin/mkitc @@ -9,25 +9,24 @@ require 'json' require 'net_http_unix' require 'securerandom' require 'erb' +require 'uri' +require 'fileutils' -class InvalidParametersException < RuntimeError +class InvalidParametersException < Exception attr_reader :command + def initialize(cause, command = nil) super(cause) @command = command end end -class MKItClient - def initialize - @client = NetX::HTTPUnix.new('localhost', 4567) - end - - def dict +class CommandPalette + def schema global_args = [ { short: '-v', long: '--verbose', help: 'verbose', mandatory: false, value: nil } ] - command_dict = [ + [ { cmd: 'ps', args: [ @@ -140,69 +139,89 @@ class MKItClient ], help: 'haproxy status and control', usage: [''] + }, + { + cmd: 'profile', + options: [ + { + cmd: 'set', + request: { verb: 'set' }, + args: [ + { name: 'profile_name', mandatory: true } + ], + help: 'set mkit client configuration profile' + }, + { + cmd: 'show', + request: { verb: 'show' }, + help: 'show mkit client current profile' + } + ], + help: 'mkit client configuration profile', + usage: ['<[set ]|[show]>'] } ] - command_dict end +end - def help(cause: nil, cmd: nil) - msg = '' - if cause.nil? - my_cmd = cmd - else - msg += "MKItc: #{cause.message}\n" - my_cmd = cause.command - end - if my_cmd.nil? - msg += "\nUsage: mkitc [options]\n\n" - msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n" - msg += "Commands:\n\n" - dict.each do |c| - msg += format("%-10s %s\n", c[:cmd], c[:help]) - end - msg += "\n" - msg += "Run 'mkitc help ' for specific command information.\n\n" - else - msg += format("\nUsage: mkitc %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' ')) - msg += format("%s\n", my_cmd[:help]) - unless my_cmd[:options].nil? - msg += "\nOptions:\n" - my_cmd[:options].each do |c| - msg += format("%-10s %s\n", c[:cmd], c[:help]) - end - end - msg += "\n" - end - puts msg - exit 1 +class MKItClient + def initialize + @root = File.expand_path('..', __dir__) + @config_dir = "#{ENV['HOME']}/.mkit" + @profile_file = "#{@config_dir}/current" + @commands = CommandPalette.new + create_default_config end - def create(request, request_hash = nil) - unless File.file?(request_hash[:file]) - raise InvalidParametersException.new('File not found.', c = dict.select { |k| k[:cmd] == 'create' }.first) + def create_default_config + unless File.exist?(@config_dir) + puts "Creating config directory on '#{@config_dir}'..." + FileUtils.mkdir_p(@config_dir) end + FileUtils.cp("#{@root}/config/mkitc_config.yml", @config_dir) unless File.exist?("#{@config_dir}/mkitc_config.yml") + profile({ verb: 'set' }, { profile_name: 'local' }) unless File.exist?(@profile_file) + end - yaml = YAML.load_file(request_hash[:file]) - if yaml['service'].nil? - raise InvalidParametersException.new('Invalid configuration file', c = dict.select { |k| k[:cmd] == 'create' }.first) - else - request(request, request_hash) + def read_configuration + current_profile = File.read(@profile_file) + if current_profile.nil? || current_profile.empty? + # force set default + profile({ verb: 'set' }, { profile_name: 'local' }) + current_profile = 'local' end - end + cfg = YAML.load_file("#{@config_dir}/mkitc_config.yml") - def update(request, request_hash = nil) - unless File.file?(request_hash[:file]) - raise InvalidParametersException.new('File not found.', c = dict.select { |k| k[:cmd] == 'update' }.first) + if cfg['mkit'].nil? || cfg['mkit'][current_profile.lstrip].nil? + raise InvalidParametersException, "invalid configuration found on '~/.mkit' or profile not found" end - yaml = YAML.load_file(request_hash[:file]) - if yaml['service'].nil? - raise InvalidParametersException.new('Invalid configuration file', c = dict.select { |k| k[:cmd] == 'update' }.first) + @configuration = cfg['mkit'][current_profile.lstrip] + end + + def client + read_configuration + uri = URI(@configuration['server.uri']) + case uri.scheme + when 'https' + @client = NetX::HTTPUnix.new(uri.host, uri.port) + @client.use_ssl = true + @client.verify_mode = OpenSSL::SSL::VERIFY_NONE + when 'http' + @client = NetX::HTTPUnix.new(uri.host, uri.port) + when 'sock' + @client = NetX::HTTPUnix.new("unix://#{uri.path}") else - id = yaml['service']['name'] - request_hash[:id] = id - request(request, request_hash) + raise InvalidParametersException, 'Invalid mkit server uri. Please check configuration' end + @client + end + + def dict + @commands.schema + end + + def find_command(cmd) + dict.select { |k| k[:cmd] == cmd }.first end def parse_args(args) @@ -211,55 +230,35 @@ class MKItClient # short circuit for help if cmd == 'help' || args.empty? if args.size > 1 - c = dict.select { |k| k[:cmd] == args[1] }.first + c = find_command(args[1]) raise InvalidParametersException, "'#{args[1]}' is not a valid help topic." if c.nil? end return help(cmd: c) else - c = dict.select { |k| k[:cmd] == cmd }.first + c = find_command(cmd) end raise InvalidParametersException, 'Command not found' if c.nil? + command = c myargs = args.dup myargs.delete(cmd) - max_args_size = c[:args].nil? ? 0 : c[:args].size - max_options_size = c[:options].nil? ? 0 : 1 - max_args_size += max_options_size - - min_args_size = c[:args].nil? ? 0 : c[:args].select { |a| a[:mandatory] == true }.size - min_options_size = c[:options].nil? ? 0 : 1 - min_args_size += min_options_size - - if myargs.size > max_args_size || myargs.size < min_args_size - raise InvalidParametersException.new('Invalid parameters found.', c) - end - request_hash = {} - request = c[:request] + request = command[:request] unless myargs.empty? - unless c[:args].nil? - idx = 0 - c[:args].each do |a| - request_hash[a[:name].to_sym] = myargs[idx] - request[:uri] = request[:uri] + a[:uri] unless a[:uri].nil? - idx += 1 - end - end # options unless c[:options].nil? - option = nil - myargs.each do |s| - option = c[:options].select { |o| o[:cmd] == s }.first - raise InvalidParametersException.new('Invalid parameters found.', c) if option.nil? || option.empty? - end - raise InvalidParametersException.new('Invalid parameters found.', c) if option.nil? || option.empty? + command = c[:options].select { |o| o[:cmd] == myargs[0] }.first + raise InvalidParametersException.new('Invalid parameters found.', c) if command.nil? || command.empty? - request = option[:request] + myargs.delete_at(0) + request = command[:request] end + fill_cmd_args(command[:args], myargs, request, request_hash) end - raise InvalidParametersException, "Can't find request." if request.nil? + raise InvalidParametersException.new('Invalid command or parameters.', c) if request.nil? + validate_command(command, request_hash) if respond_to? c[:cmd] send(c[:cmd], request, request_hash) else @@ -267,11 +266,25 @@ class MKItClient end end - def doIt(args) - result = parse_args(args) - puts result - rescue InvalidParametersException => e - help(cause: e) + def fill_cmd_args(args, myargs, request, request_hash) + return if args.nil? + + idx = 0 + args.each do |a| + request_hash[a[:name].to_sym] = myargs[idx] + request[:uri] = request[:uri] + a[:uri] unless a[:uri].nil? + idx += 1 + end + end + + def validate_command(command, request_hash) + return if command[:args].nil? + + command[:args].select { |a| a[:mandatory] == true }.each do |a| + if request_hash[a[:name].to_sym].nil? + raise InvalidParametersException.new("Missing mandatory parameter: #{a[:name]}", command) + end + end end def request(request, request_args = nil) @@ -304,7 +317,7 @@ class MKItClient when :delete req = Net::HTTP::Delete.new(uri) end - @client.request(req).body + client.request(req).body end def attach(file) @@ -318,6 +331,106 @@ class MKItClient body << "\r\n--#{boundary}--\r\n" [body.join, boundary] end + + def doIt(args) + result = parse_args(args) + puts result + rescue InvalidParametersException => e + help(cause: e) + end + + def help(cause: nil, cmd: nil) + msg = '' + if cause.nil? + my_cmd = cmd + else + msg += "MKItc: #{cause.message}\n" + my_cmd = cause.command + end + if my_cmd.nil? + msg += "\nUsage: mkitc [options]\n\n" + msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n" + msg += "Commands:\n\n" + dict.each do |c| + msg += format("%-10s %s\n", c[:cmd], c[:help]) + end + msg += "\n" + msg += "Run 'mkitc help ' for specific command information.\n\n" + else + msg += format("\nUsage: mkitc %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' ')) + msg += format("%s\n", my_cmd[:help]) + unless my_cmd[:options].nil? + msg += "\nOptions:\n" + my_cmd[:options].each do |c| + msg += format("%-10s %s\n", c[:cmd], c[:help]) + end + end + msg += "\n" + end + puts msg + exit 1 + end + + def create(request, request_hash = nil) + unless File.file?(request_hash[:file]) + raise InvalidParametersException.new('File not found.', find_command('create')) + end + + yaml = YAML.load_file(request_hash[:file]) + if yaml['service'].nil? + raise InvalidParametersException.new('Invalid configuration file', find_command('create')) + else + request(request, request_hash) + end + end + + def update(request, request_hash = nil) + unless File.file?(request_hash[:file]) + raise InvalidParametersException.new('File not found.', find_command('update')) + end + + yaml = YAML.load_file(request_hash[:file]) + if yaml['service'].nil? + raise InvalidParametersException.new('Invalid configuration file', find_command('update')) + else + id = yaml['service']['name'] + request_hash[:id] = id + request(request, request_hash) + end + end + + def profile(request, request_hash = {}) + cfg = YAML.load_file("#{@config_dir}/mkitc_config.yml") + cmd = find_command('profile') + if cfg['mkit'].nil? + raise InvalidParametersException.new( + "Invalid configuration on '~/.mkit'\nPlease fix or clean up for defaults apply", cmd + ) + end + + case request[:verb] + when 'set' + profile = request_hash[:profile_name] + if cfg['mkit'][profile.lstrip].nil? + raise InvalidParametersException.new("Profile not found on '~/.mkit' configuration", cmd) + end + + puts "Setting current profile to #{profile}." + File.write(@profile_file, request_hash[:profile_name]) + '' + when 'show' + active = File.read("#{@config_dir}/current") + cfg['mkit'].map do |k, _v| + if k == active + "*#{k}" + else + k + end + end.join(' ') + else + raise InvalidParametersException.new("Invalid 'profile' operation", cmd) + end + end end # diff --git a/config/mkitc_config.yml b/config/mkitc_config.yml new file mode 100644 index 0000000..5a2c5c8 --- /dev/null +++ b/config/mkitc_config.yml @@ -0,0 +1,3 @@ +mkit: + local: + server.uri: http://localhost:4567 diff --git a/config/mkitd_config.sh b/config/mkitd_config.sh index 50ee22c..d52b7e8 100644 --- a/config/mkitd_config.sh +++ b/config/mkitd_config.sh @@ -1,5 +1,5 @@ # -# kidsd server options (for systemd unit | daemontools) +# mkitd server options (for systemd unit | daemontools) # OPTIONS="" diff --git a/lib/mkit/version.rb b/lib/mkit/version.rb index a94cd13..659fd35 100644 --- a/lib/mkit/version.rb +++ b/lib/mkit/version.rb @@ -1,4 +1,4 @@ module MKIt - VERSION = "0.4.3" + VERSION = "0.5.0" end