mkit 0.6.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ require 'yaml'
4
+ require 'json'
5
+ require 'erb'
6
+
7
+ class InvalidParametersException < Exception
8
+ attr_reader :command
9
+
10
+ def initialize(cause, command = nil)
11
+ super(cause)
12
+ @command = command
13
+ end
14
+ end
15
+
16
+ class CommandParser
17
+ def initialize
18
+ @dict = YAML.safe_load(File.read("#{File.expand_path('..', __dir__)}/client/commands.yaml"), symbolize_names: true)
19
+ end
20
+
21
+ def dict
22
+ @dict
23
+ end
24
+
25
+ def parse(args)
26
+ cmd = args[0]
27
+ c = nil
28
+ # short circuit for help
29
+ if cmd == 'help' || args.empty?
30
+ if args.size > 1
31
+ c = find_command(args[1])
32
+ raise InvalidParametersException, "'#{args[1]}' is not a valid help topic." if c.nil?
33
+ end
34
+ return help(cmd: c)
35
+ else
36
+ c = find_command(cmd)
37
+ end
38
+ raise InvalidParametersException, 'Command not found' if c.nil?
39
+
40
+ command = c
41
+ argv = args.dup
42
+ argv.delete(cmd)
43
+
44
+ request_data = {}
45
+ request = command[:request]
46
+ unless argv.empty?
47
+ # options
48
+ unless c[:options].nil?
49
+ command = c[:options].select { |o| o[:cmd] == argv[0] }.first
50
+ raise InvalidParametersException.new('Invalid parameters found.', c) if command.nil? || command.empty?
51
+
52
+ argv.delete_at(0)
53
+ request = command[:request]
54
+ end
55
+ fill_cmd_args(command[:args], argv, request, request_data)
56
+ end
57
+ raise InvalidParametersException.new('Invalid command or parameters.', c) if request.nil?
58
+
59
+ validate_command(command, request_data)
60
+ #
61
+ {
62
+ cmd: c[:cmd],
63
+ request: request,
64
+ data: request_data
65
+ }
66
+ end
67
+
68
+ # args = command[:args]
69
+ # argv = ARGV.dup - cmd
70
+ # request = command[:request]
71
+ # request_data = {}
72
+ def fill_cmd_args(args, argv, request, request_data)
73
+ return if args.nil?
74
+ # add to schema
75
+ args.each do |arg|
76
+ arg[:type] = 'value' unless arg[:type]
77
+ end
78
+ # flag and options
79
+ fill_flag_and_options_args(args, argv, request, request_data)
80
+ idx = 0
81
+ args.each do |arg|
82
+ if arg[:type].to_sym == :value
83
+ request_data[arg[:name].to_sym] = argv[idx]
84
+ fill_params_and_uri(arg, request)
85
+ end
86
+
87
+ idx += 1
88
+ end
89
+ end
90
+
91
+ def fill_flag_and_options_args(args, argv, request, request_data)
92
+ # flags
93
+ # checking flags first, avoids -n -f, with -f being the value of -n
94
+ args.select { |arg| arg[:type].to_sym == :flag }.each do |arg|
95
+ idx = find_option_or_flag_index(arg, argv)
96
+ if idx
97
+ fill_params_and_uri(arg, request)
98
+ argv.delete_at(idx)
99
+ request_data[arg[:name].to_sym] = arg[:param]
100
+ end
101
+ end
102
+ # options
103
+ args.select { |arg| arg[:type].to_sym == :option }.each do |arg|
104
+ idx = find_option_or_flag_index(arg, argv)
105
+ if idx
106
+ fill_params_and_uri(arg, request)
107
+ argv.delete_at(idx)
108
+ request_data[arg[:name].to_sym] = argv[idx]
109
+ end
110
+ end
111
+ end
112
+
113
+ def find_option_or_flag_index(arg, argv)
114
+ idx = nil
115
+ arg[:switch].each { | switch |
116
+ idx ||= argv.index(switch)
117
+ }
118
+ idx
119
+ end
120
+
121
+ def fill_params_and_uri(arg, request)
122
+ request[:uri] = request[:uri] + arg[:uri] unless arg[:uri].nil?
123
+ unless arg[:param].nil?
124
+ request[:params] ||= []
125
+ request[:params] << [ "#{arg[:name]}", "#{arg[:param]}"]
126
+ end
127
+ end
128
+
129
+ def validate_command(command, request_data)
130
+ return if command[:args].nil?
131
+
132
+ command[:args].select { |arg| arg[:mandatory] == true }.each do |arg|
133
+ if request_data[arg[:name].to_sym].nil?
134
+ raise InvalidParametersException.new("Missing mandatory parameter: #{arg[:name]}", command)
135
+ end
136
+ end
137
+ request_data.select{|key, value| value.nil? }.each do |key, value|
138
+ raise InvalidParametersException.new("Missing parameter value for #{key}", command)
139
+ end
140
+ end
141
+
142
+ def find_command(cmd)
143
+ dict.select { |k| k[:cmd] == cmd }.first
144
+ end
145
+
146
+ def doIt(args)
147
+ result = parse_args(args)
148
+ puts result
149
+ rescue InvalidParametersException => e
150
+ help(cause: e)
151
+ end
152
+
153
+ def format_arg_help_msg(arg)
154
+ _format(arg[:help][0], arg[:help][1])
155
+ end
156
+
157
+ def format_cmd_help_msg(command)
158
+ _format(command[:cmd], command[:help])
159
+ end
160
+
161
+ def _format(arg1, srg2)
162
+ format("%-12s %s\n", arg1, srg2)
163
+ end
164
+
165
+ def help(cause: nil, cmd: nil)
166
+ msg = ''
167
+ if cause.nil?
168
+ my_cmd = cmd
169
+ else
170
+ msg += "MKIt: #{cause.message}\n"
171
+ my_cmd = cause.command
172
+ end
173
+ if my_cmd.nil?
174
+ msg += "\nUsage: mkit <command> [options]\n\n"
175
+ msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n"
176
+ msg += "Commands:\n\n"
177
+ dict.each do |c|
178
+ msg += format_cmd_help_msg(c)
179
+ end
180
+ msg += "\n"
181
+ msg += "Run ' mkit help <command>' for specific command information.\n\n"
182
+ else
183
+ # todo mkit help profile set
184
+ msg += format("\nUsage: mkit %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' '))
185
+ msg += format("%s\n", my_cmd[:help])
186
+ if !my_cmd[:options].nil? || !my_cmd[:args].nil?
187
+ msg += "\nOptions:\n"
188
+ # command
189
+ unless my_cmd[:options].nil?
190
+ my_cmd[:options].each do |c|
191
+ msg += format_cmd_help_msg(c)
192
+ end
193
+ end
194
+ # args
195
+ unless my_cmd[:args].nil?
196
+ # values only first
197
+ cmd_args = my_cmd[:args].select{ |arg| (arg[:type].nil? || arg[:type].to_sym == :value) && !arg[:help].nil?}
198
+ cmd_args.each do |arg|
199
+ msg += format_arg_help_msg(arg)
200
+ end
201
+ cmd_args = my_cmd[:args].select{ |arg| !arg[:type].nil? && (arg[:type].to_sym == :option || arg[:type].to_sym == :flag)}
202
+ cmd_args.each do |arg|
203
+ msg += format_arg_help_msg(arg)
204
+ end
205
+ end
206
+ end
207
+ msg += "\n"
208
+ end
209
+ puts msg
210
+ exit 1
211
+ end
212
+
213
+ end
214
+
@@ -0,0 +1,203 @@
1
+ ---
2
+ - cmd: init
3
+ help: init mkit client
4
+ request: {}
5
+ - cmd: ps
6
+ usage:
7
+ - "[service_id_or_name]"
8
+ help: show services status (alias for status)
9
+ args:
10
+ - name: id
11
+ help:
12
+ - id
13
+ - Service id or name
14
+ mandatory: false
15
+ uri: "/<%=id%>"
16
+ request:
17
+ verb: get
18
+ uri: "/services"
19
+ - cmd: status
20
+ usage:
21
+ - "[service_id_or_name]"
22
+ help: show services status
23
+ args:
24
+ - name: id
25
+ help:
26
+ - id
27
+ - Service id or name
28
+ mandatory: false
29
+ uri: "/<%=id%>"
30
+ request:
31
+ verb: get
32
+ uri: "/services"
33
+ - cmd: start
34
+ usage:
35
+ - "<service_id_or_name>"
36
+ help: start service
37
+ args:
38
+ - name: id
39
+ help:
40
+ - id
41
+ - Service id or name
42
+ mandatory: true
43
+ request:
44
+ verb: put
45
+ uri: "/services/<%=id%>/start"
46
+ - cmd: stop
47
+ usage:
48
+ - "<service_id_or_name>"
49
+ help: stop service
50
+ args:
51
+ - name: id
52
+ help:
53
+ - id
54
+ - Service id or name
55
+ mandatory: true
56
+ request:
57
+ verb: put
58
+ uri: "/services/<%=id%>/stop"
59
+ - cmd: restart
60
+ usage:
61
+ - "<service_id_or_name>"
62
+ help: restart service
63
+ args:
64
+ - name: id
65
+ help:
66
+ - id
67
+ - Service id or name
68
+ mandatory: true
69
+ request:
70
+ verb: put
71
+ uri: "/services/<%=id%>/restart"
72
+ - cmd: create
73
+ usage:
74
+ - "<service.yaml>"
75
+ help: create new service
76
+ args:
77
+ - name: file
78
+ help:
79
+ - file
80
+ - Service definition
81
+ mandatory: true
82
+ request:
83
+ verb: post
84
+ uri: "/services"
85
+ - cmd: update
86
+ usage:
87
+ - "<service.yaml>"
88
+ help: update service
89
+ args:
90
+ - name: file
91
+ help:
92
+ - file
93
+ - Service definition
94
+ mandatory: true
95
+ request:
96
+ verb: put
97
+ uri: "/services/<%=id%>"
98
+ - cmd: rm
99
+ usage:
100
+ - "<service_id_or_name>"
101
+ help: remove service
102
+ args:
103
+ - name: id
104
+ help:
105
+ - id
106
+ - Service id or name
107
+ mandatory: true
108
+ request:
109
+ verb: delete
110
+ uri: "/services/<%=id%>"
111
+ - cmd: logs
112
+ usage:
113
+ - "<service_id_or_name> [-f] [-n <lines>]"
114
+ help: view service logs
115
+ request:
116
+ verb: ws # new type
117
+ uri: "/services/<%=id%>/logs"
118
+ args:
119
+ - name: id
120
+ mandatory: true
121
+ # uri: "/<%=id%>" # if exists, add it to main
122
+ type: value # option | flag | value # option takes a value, flag does not takes a value, value is like id_or_name
123
+ help:
124
+ - id
125
+ - Service id or name
126
+ - name: follow
127
+ help:
128
+ - -f
129
+ - Follow log output
130
+ mandatory: false
131
+ param: "<%=true%>" # templated. param ou uri - param -> add query parameter name=param_value
132
+ type: flag
133
+ switch:
134
+ - "-f"
135
+ - name: nr_lines
136
+ help:
137
+ - -n <lines>
138
+ - Number of lines to show from the end of the logs
139
+ mandatory: false
140
+ param: "<%=nr_lines%>"
141
+ type: option
142
+ switch:
143
+ - "-n"
144
+ - name: pods
145
+ help:
146
+ - -p <[pods]>
147
+ - Show logs for specified logs, e.g. pod1, pod2 (default first)
148
+ mandatory: false
149
+ param: "<%=pods%>"
150
+ type: option
151
+ switch:
152
+ - "-n"
153
+ - cmd: version
154
+ help: prints mkit server version
155
+ request:
156
+ verb: get
157
+ uri: "/mkit/version"
158
+ - cmd: proxy
159
+ usage:
160
+ - "<start|stop|restart|status>"
161
+ help: haproxy status and control
162
+ options:
163
+ - cmd: start
164
+ help: start proxy service
165
+ request:
166
+ verb: put
167
+ uri: "/mkit/proxy/start"
168
+ - cmd: stop
169
+ help: stop proxy service
170
+ request:
171
+ verb: put
172
+ uri: "/mkit/proxy/stop"
173
+ - cmd: restart
174
+ help: restarts proxy service
175
+ request:
176
+ verb: put
177
+ uri: "/mkit/proxy/restart"
178
+ - cmd: status
179
+ help: proxy service status
180
+ request:
181
+ verb: get
182
+ uri: "/mkit/proxy/status"
183
+ - cmd: profile
184
+ usage:
185
+ - "<[set <profile_name>]|[show]>"
186
+ help: mkit client configuration profile
187
+ options:
188
+ - cmd: set
189
+ help: set mkit client configuration profile
190
+ usage:
191
+ - "<profile>"
192
+ request:
193
+ verb: set
194
+ args:
195
+ - name: profile
196
+ help:
197
+ - profile
198
+ - profile name
199
+ mandatory: true
200
+ - cmd: show
201
+ help: show mkit client current profile
202
+ request:
203
+ verb: show
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'net_http_unix'
7
+ require 'securerandom'
8
+ require 'erb'
9
+ require 'uri'
10
+ require 'fileutils'
11
+
12
+ module MKIt
13
+ class HttpClient
14
+ def initialize(server_url, my_id)
15
+ @server_url = server_url
16
+ @my_id = my_id
17
+ end
18
+
19
+ #
20
+ # http client
21
+ #
22
+
23
+ def client(req)
24
+ req['X-API-KEY'] = @my_id
25
+ uri = URI(@server_url)
26
+ case uri.scheme
27
+ when 'https'
28
+ @client = NetX::HTTPUnix.new(uri.host, uri.port)
29
+ @client.use_ssl = true
30
+ @client.verify_mode = OpenSSL::SSL::VERIFY_NONE
31
+ when 'http'
32
+ @client = NetX::HTTPUnix.new(uri.host, uri.port)
33
+ when 'sock'
34
+ @client = NetX::HTTPUnix.new("unix://#{uri.path}")
35
+ else
36
+ raise InvalidParametersException, 'Invalid mkit server uri. Please check configuration'
37
+ end
38
+ @client.request(req)
39
+ end
40
+
41
+ def request(request, request_data = nil)
42
+ req = nil
43
+ uri = request[:uri]
44
+ request[:file] = request_data[:file]
45
+
46
+ unless request[:params].nil? || request[:params].empty?
47
+ uri = uri + '?' + request[:params].map { |k, v| "#{k}=#{v}" }.join('&')
48
+ end
49
+ uri = ERB.new(uri).result_with_hash(request_data)
50
+ case request[:verb].to_sym
51
+ when :post
52
+ req = Net::HTTP::Post.new(uri)
53
+ unless request[:file].nil?
54
+ (body, boundary) = attach(request[:file])
55
+ req.body = body
56
+ req['Content-Type'] = "multipart/form-data, boundary=#{boundary}"
57
+ end
58
+ when :put
59
+ req = Net::HTTP::Put.new(uri)
60
+ unless request[:file].nil?
61
+ (body, boundary) = attach(request[:file])
62
+ req.body = body
63
+ req['Content-Type'] = "multipart/form-data, boundary=#{boundary}"
64
+ end
65
+ when :patch
66
+ req = Net::HTTP::Patch.new(uri)
67
+ when :get
68
+ req = Net::HTTP::Get.new(uri)
69
+ when :delete
70
+ req = Net::HTTP::Delete.new(uri)
71
+ when :ws
72
+
73
+ end
74
+ client(req).body
75
+ end
76
+
77
+ def attach(file)
78
+ boundary = SecureRandom.alphanumeric
79
+ body = []
80
+ body << "--#{boundary}\r\n"
81
+ body << "Content-Disposition: form-data; name=file; filename='#{File.basename(file)}'\r\n"
82
+ body << "Content-Type: text/plain\r\n"
83
+ body << "\r\n"
84
+ body << File.read(file)
85
+ body << "\r\n--#{boundary}--\r\n"
86
+ [body.join, boundary]
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require 'mkit/client/http_client'
3
+ require 'mkit/client/websocket_client'
4
+
5
+ module MKIt
6
+ class MKItdClient
7
+ def initialize(request, server_url, my_id)
8
+ case request[:verb].to_sym
9
+ when :ws
10
+ @client = MKIt::WebSocketClient.new(server_url, my_id)
11
+ else
12
+ @client = MKIt::HttpClient.new(server_url, my_id)
13
+ end
14
+ end
15
+
16
+ def request(request, request_data)
17
+ @client.request(request, request_data)
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,68 @@
1
+ # # frozen_string_literal: true
2
+ require 'faye/websocket'
3
+ require 'eventmachine'
4
+ require 'json'
5
+
6
+ module MKIt
7
+ class WebSocketClient
8
+ def initialize(server_url, my_id)
9
+ @server_url = server_url
10
+ @my_id = my_id
11
+ uri = URI(@server_url)
12
+ use_ssl = uri.scheme == 'https'
13
+ @options = use_ssl ? { tls: { :verify_peer => false } } : {}
14
+ @options[:headers] = { 'X-API-KEY' => @my_id }
15
+ url_prefix = use_ssl ? "wss" : "ws"
16
+ @ws_url = "#{url_prefix}://#{uri.host}:#{uri.port}"
17
+ trap("SIGINT") do
18
+ puts "Bye..."
19
+ EventMachine.stop
20
+ end
21
+ end
22
+
23
+ def request(request, request_data)
24
+ uri = request[:uri]
25
+ unless request[:params].nil? || request[:params].empty?
26
+ uri = uri + '?' + request[:params].map { |k, v| "#{k}=#{v}" }.join('&')
27
+ end
28
+ uri = ERB.new("#{@ws_url}#{uri}").result_with_hash(request_data)
29
+
30
+ EM.run {
31
+ ws = Faye::WebSocket::Client.new(uri, nil, @options)
32
+
33
+ ws.on :open do |event|
34
+ # no op
35
+ end
36
+
37
+ ws.on :message do |event|
38
+ puts event.data
39
+ end
40
+
41
+ ws.on :error do |event|
42
+ p [:error, event.message]
43
+ ws = nil
44
+ return
45
+ end
46
+
47
+ ws.on :close do |event|
48
+ ws = nil
49
+ EventMachine.stop
50
+ end
51
+
52
+ Thread.new do
53
+ loop do
54
+ input = STDIN.gets.chomp
55
+ if input == 'exit'
56
+ puts "bye..."
57
+ EventMachine.stop
58
+ break
59
+ end
60
+ end
61
+ end
62
+ }
63
+ end
64
+ end
65
+ end
66
+
67
+
68
+
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+ module MKIt
3
+ class ExitShell < RuntimeError; end
4
+
5
+ class ShellClient
6
+ def initialize(command:)
7
+ @command = command
8
+ @logger = MKItLogger
9
+ @logger.debug("Command initialized: [#{@command}]")
10
+ end
11
+
12
+ def unregister
13
+ @logger.info("ending [#{@command}]...")
14
+ if @client
15
+ begin
16
+ @client.raise ExitShell.new
17
+ rescue
18
+ @logger.error("Failed to raise ExitShell")
19
+ end
20
+ @client.exit rescue @logger.warn("Failed to exit client thread")
21
+ end
22
+ end
23
+
24
+ def close
25
+ # no op
26
+ end
27
+
28
+ def register
29
+ @client = Thread.new {
30
+ begin
31
+ PTY.spawn( @command ) do |stdout, stdin, pid |
32
+ begin
33
+ yield stdout, stdin, pid if block_given?
34
+ rescue Errno::EIO
35
+ @logger.warn(
36
+ "Errno:EIO error, but this probably just means that the process has finished giving output"
37
+ )
38
+ rescue ExitShell
39
+ @logger.info("#{@command} ended")
40
+ ensure
41
+ stdin.close rescue @logger.warn("Failed to close stdin")
42
+ stdout.close rescue @logger.warn("Failed to close stdout")
43
+ begin
44
+ close
45
+ rescue => e
46
+ @logger.warn("Error closing client", e)
47
+ end
48
+ Process.wait(pid)
49
+ end
50
+ end
51
+ rescue PTY::ChildExited
52
+ @logger.info("#{@command} exited.")
53
+ end
54
+ }
55
+ end
56
+ end
57
+ end
@@ -1,5 +1,6 @@
1
1
  require 'pty'
2
2
  require 'mkit/status'
3
+ require "mkit/cmd/shell_client"
3
4
 
4
5
  #
5
6
  # https://docs.docker.com/engine/reference/commandline/events
@@ -12,6 +13,7 @@ class StopThread < RuntimeError; end
12
13
 
13
14
  def initialize
14
15
  @queue = Queue.new
16
+ @listener = ShellClient.new(command: "docker events --format '{{json .}}'")
15
17
  end
16
18
 
17
19
  def enqueue(msg)
@@ -20,11 +22,11 @@ class StopThread < RuntimeError; end
20
22
 
21
23
  def start
22
24
  @consumer.run if register_consumer
23
- @listener.run if register_listener
25
+ register_listener
24
26
  end
25
27
 
26
28
  def stop
27
- @listener.exit if @listener
29
+ @listener.unregister if @listener
28
30
  @consumer.raise StopThread.new
29
31
  MKItLogger.info("docker listener stopped")
30
32
  end
@@ -107,25 +109,9 @@ class StopThread < RuntimeError; end
107
109
  end
108
110
 
109
111
  def register_listener
110
- return false unless @listener.nil?
111
-
112
- @listener = Thread.new {
113
- cmd = "docker events --format '{{json .}}'"
114
- begin
115
- PTY.spawn( cmd ) do |stdout, stdin, pid|
116
- begin
117
- stdout.each { |line| enqueue JSON.parse(line).to_o }
118
- rescue Errno::EIO
119
- MKItLogger.warn("Errno:EIO error, but this probably just means " +
120
- "that the process has finished giving output")
121
- end
122
- end
123
- rescue PTY::ChildExited
124
- MKItLogger.warn("docker event listener process exited!")
125
- end
126
- }
127
- MKItLogger.info("docker listener started")
128
- true
112
+ @listener.register do |stdout, stdin, pid|
113
+ stdout.each { |line| enqueue JSON.parse(line).to_o }
114
+ end
129
115
  end
130
116
  end
131
117
  end