#!/usr/bin/env ruby # frozen_string_literal: true require 'rubygems' require 'optparse' require 'yaml' require 'net/http' require 'json' require 'net_http_unix' require 'securerandom' require 'erb' class InvalidParametersException < RuntimeError 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 global_args = [ { short: '-v', long: '--verbose', help: 'verbose', mandatory: false, value: nil } ] command_dict = [ { cmd: 'ps', args: [ { name: 'id', mandatory: false, uri: '/<%=id%>' } ], help: 'show services status (alias for status)', usage: ['[service_id_or_name]'], request: { verb: :get, uri: '/services' } }, { cmd: 'status', args: [ { name: 'id', mandatory: false, uri: '/<%=id%>' } ], help: 'show services status', usage: ['[service_id_or_name]'], request: { verb: :get, uri: '/services' } }, { cmd: 'start', args: [ { name: 'id', mandatory: true } ], help: 'start service', usage: [''], request: { verb: :put, uri: '/services/<%=id%>/start' } }, { cmd: 'stop', args: [ { name: 'id', mandatory: true } ], help: 'stop service', usage: [''], request: { verb: :put, uri: '/services/<%=id%>/stop' } }, { cmd: 'restart', args: [ { name: 'id', mandatory: true } ], help: 'restart service', usage: [''], request: { verb: :put, uri: '/services/<%=id%>/restart' } }, { cmd: 'create', args: [ { name: 'file', mandatory: true } ], help: 'create new service', usage: [''], request: { verb: :post, uri: '/services' } }, { cmd: 'update', args: [ { name: 'file', mandatory: true } ], help: 'update service', usage: [''], request: { verb: :put, uri: '/services/<%=id%>' } }, { cmd: 'rm', args: [ { name: 'id', mandatory: true } ], help: 'remove service', usage: [''], request: { verb: :delete, uri: '/services/<%=id%>' } }, { cmd: 'version', help: 'prints mkit server version', request: { verb: :get, uri: '/mkit/version' } }, { cmd: 'proxy', options: [ { cmd: 'start', request: { verb: :put, uri: '/mkit/proxy/start' }, help: 'start proxy service' }, { cmd: 'stop', request: { verb: :put, uri: '/mkit/proxy/stop' }, help: 'stop proxy service' }, { cmd: 'restart', request: { verb: :put, uri: '/mkit/proxy/restart' }, help: 'restarts proxy service' }, { cmd: 'status', request: { verb: :get, uri: '/mkit/proxy/status' }, help: 'proxy service status' } ], help: 'haproxy status and control', usage: [''] } ] command_dict 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.', c = dict.select { |k| k[:cmd] == 'create' }.first) 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) end end 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) 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) else id = yaml['service']['name'] request_hash[:id] = id request(request, request_hash) end end def parse_args(args) cmd = args[0] c = nil # short circuit for help if cmd == 'help' || args.empty? if args.size > 1 c = dict.select { |k| k[:cmd] == args[1] }.first 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 end raise InvalidParametersException, 'Command not found' if c.nil? 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] 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? request = option[:request] end end raise InvalidParametersException, "Can't find request." if request.nil? if respond_to? c[:cmd] send(c[:cmd], request, request_hash) else request(request, request_hash) end end def doIt(args) result = parse_args(args) puts result rescue InvalidParametersException => e help(cause: e) end def request(request, request_args = nil) req = nil uri = ERB.new(request[:uri]).result_with_hash(request_args) request[:file] = request_args[:file] unless request[:params].nil? || request[:params].empty? uri = uri + '?' + request[:params].map { |k, v| "#{k}=#{v}" }.join('&') end case request[:verb] when :post req = Net::HTTP::Post.new(uri) unless request[:file].nil? (body, boundary) = attach(request[:file]) req.body = body req['Content-Type'] = "multipart/form-data, boundary=#{boundary}" end when :put req = Net::HTTP::Put.new(uri) unless request[:file].nil? (body, boundary) = attach(request[:file]) req.body = body req['Content-Type'] = "multipart/form-data, boundary=#{boundary}" end when :patch req = Net::HTTP::Patch.new(uri) when :get req = Net::HTTP::Get.new(uri) when :delete req = Net::HTTP::Delete.new(uri) end @client.request(req).body end def attach(file) boundary = SecureRandom.alphanumeric body = [] body << "--#{boundary}\r\n" body << "Content-Disposition: form-data; name=file; filename='#{File.basename(file)}'\r\n" body << "Content-Type: text/plain\r\n" body << "\r\n" body << File.read(file) body << "\r\n--#{boundary}--\r\n" [body.join, boundary] end end # # go # client = MKItClient.new client.doIt(ARGV.dup) # # if ARGV.any? # parse args # host, socket, config file # end