mkit 0.4.3 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb997b1bc61151dde6209eeaefdf4f669df9255b7e69735eb0c5c56e346222f1
4
- data.tar.gz: 3819a29d9335a2d4831051d133d9e0056b93d54803701a27ed596a750f907d7b
3
+ metadata.gz: ca554f25f295a7403d5f0d654663d466430a361c8aa2629961f1710579e62506
4
+ data.tar.gz: d670fcc4523810439d73244276766a8e5ff70f2b8a052b292941014a11e7ca3b
5
5
  SHA512:
6
- metadata.gz: c95a967e4a016554700b9e844de87481fd8f4b1f71e2cb4cdae16509104b177485f6cdcc3aa114f3d4ab8680e8d39fb05c125fca4dd81edbb089800d08ec275e
7
- data.tar.gz: f62ac16133370c7bc41f901fe148b708c046f4598331848daefd4bb108419f7b2fc510a9176bff771fbff3afa4e38d5ce000d70d8cad1feeb41b8f339956d802
6
+ metadata.gz: 825f5736fe72ce3bec3aab11f5edfa75b286a6e08ff4e6dc44be37c6d11a034e84a0910fca6ce78cb05ead4483ec228c368a749a301793396ddfc55043fc5bf9
7
+ data.tar.gz: 5b0c25f145e1c5ec19d8242d8e613c6746fc3ea25f4f1df0f39dc434ee43e56d480b399075230154d547abad466555f884077f1a1fff08590005645a762b3279
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  This is micro kubernetes(tm) on Ruby(tm), a simple tool to deploy containers to mimic a (very) minimalistic k8 cluster with a nice REST API.
4
4
 
5
+ It's also a frontend for `docker`, providing an easier way for your services to be locally available, without the need to care about local `ports` availability.
6
+
5
7
  It contains an internal DNS and uses HAProxy for routing/balancing/fail-over for Pods access.
6
8
  The database is a simple sqlite3 db and the server is a Sinatra based application.
7
9
 
@@ -16,86 +18,57 @@ The daemon is responsible for HAProxy pods routing configuration. It also provid
16
18
  * Docker
17
19
  * Linux (iproute2 package)
18
20
 
21
+ **Note:** in order to have **ssl support**, you must install `openssl-dev` package (e.g. `libssl-dev` on Ubuntu) prior to install MKIt gem.
22
+
19
23
  ## Install
20
24
 
21
- This is a simple ruby gem, so to install run
25
+ This is a simple ruby gem, so to install execute:
22
26
  ```
23
27
  # gem install mkit
24
28
  ```
25
29
 
26
- ## Running
27
-
28
- The `daemon` requires `root` user (due to `ip` and `haproxy`), you can run it directly on the repository root...
29
-
30
- ```
31
- # ./mkitd --help
32
- Usage: mkitd [options]
33
- -c config-dir set the config dir (default is /etc/mkit)
34
- -p port set the port (default is 4567)
35
- -b bind specify bind address (e.g. /tmp/app.sock)
36
- -s server specify rack server/handler
37
- -q turn on quiet mode (default is off)
38
- -x turn on the mutex lock (default is off)
39
- -e env set the environment (default is development)
40
- -o addr set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))
41
-
42
- ```
43
-
44
- or after the `gem install mkit-<version>.gem`. The server and client will be installed on host.
45
-
46
- ```
47
- # mkitd
48
- ...
49
- 0.65s info: MKIt is up and running! [ec=0xbe0] [pid=45804] [2023-12-29 15:46:04 +0000]
50
- ```
30
+ ## Configuration
51
31
 
52
- There's also samples on the samples dir, for daemontools and systemd.
32
+ ### Server configuration
53
33
 
54
- ### Accessing the API
34
+ On startup, [the server configuration](config) will be created on `/etc/mkit`.
55
35
 
56
- A client is provided to interact with mkit server.
36
+ The server will available by default on `https://localhost:4567` but you can configure server startup parameters on `/etc/mkit/mkitd_config.sh`
57
37
 
58
- Run `mkitc help` to list current supported commands.
38
+ Please check [systemd](samples/systemd) or [daemontools](samples/daemontools) directories for more details.
59
39
 
60
40
  ```
61
- Usage: mkitc <command> [options]
62
-
63
- Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster
64
-
65
- Commands:
66
-
67
- ps show services status (alias for status)
68
- status show services status
69
- logs prints service logs
70
- start start service
71
- stop stop service
72
- restart restart service
73
- create create new service
74
- update update service
75
- rm remove service
76
- version prints mkit server version
77
- proxy haproxy status and control
78
-
79
- Run 'mkitc help <command>' for specific command information.
41
+ # /etc/mkit/mkitd_config.sh
42
+ #
43
+ # mkitd server options (for systemd unit | daemontools)
44
+ #
45
+ OPTIONS=""
46
+ # e.g. OPTIONS="-b 0.0.0.0"
80
47
  ```
81
-
82
- Example:
48
+ HAProxy config directory and control commands are defined on [mkit_config.yml](config/mkit_config.yml)
83
49
 
84
50
  ```
85
- $ mkitc ps postgres
86
- +----+----------+---------------+----------+--------------+---------+
87
- | id | name | addr | ports | pods | status |
88
- +----+----------+---------------+----------+--------------+---------+
89
- | 2 | postgres | 10.210.198.10 | tcp/4532 | 49b5e4c8f247 | RUNNING |
90
- +----+----------+---------------+----------+--------------+---------+
51
+ # /etc/mkit/mkit_config.yml - mkit server configuration file.
52
+ mkit:
53
+ my_network:
54
+ ip: 10.210.198.1
55
+ haproxy:
56
+ config_dir: /etc/haproxy/haproxy.d
57
+ ctrl:
58
+ start: systemctl start haproxy
59
+ stop: systemctl stop haproxy
60
+ reload: systemctl reload haproxy
61
+ restart: systemctl restart haproxy
62
+ status: systemctl status haproxy
63
+ database:
64
+ env: development
65
+ clients:
66
+ - id: client_1_id
67
+ - id: client_2_id
68
+ - ...
91
69
  ```
92
- The service `postgres` is available on IP `10.210.198.10:5432`
93
-
94
- ## Configuration
95
-
96
- 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`
97
70
 
98
- You must configure `haproxy` to use config directory. e.g. on Ubuntu
71
+ You must configure `haproxy` to use config directory. for example on Ubuntu:
99
72
 
100
73
  ```
101
74
  # /etc/default/haproxy
@@ -112,6 +85,41 @@ CONFIG="/etc/haproxy/haproxy.d"
112
85
  #EXTRAOPTS="-de -m 16"
113
86
  ```
114
87
 
88
+ #### Authorization
89
+
90
+ To access MKIt server API, you must add each client `id` to server configuration:
91
+
92
+ ```
93
+ # /etc/mkit/mkit_config.yml - mkit server configuration file.
94
+ mkit:
95
+ my_network:
96
+ ...
97
+ clients:
98
+ - id: client_1_id
99
+ - id: client_2_id
100
+ - ...
101
+ ```
102
+
103
+ ### Client configuration
104
+
105
+ On `mkitc` first call, default configuration will be copied to `$HOME/.mkit` with `local`default profile set.
106
+
107
+ You must call `mkitc init` to initialize client configuration.
108
+
109
+ Client identification key (`my_id`) will be generated, printed out to console and saved to the client's configuration file.
110
+
111
+ You may edit the local configuration file to add more servers and change active profile with `$mkitc profile set <profile_name>`, e.g. `$mkitc profile set server_2`
112
+
113
+ ```
114
+ # ~/.mkit/mkitc_config.yml
115
+ mkit:
116
+ local:
117
+ server.uri: https://localhost:4567
118
+ server_2: # you can add more servers. change the client active profile with mkitc profile command
119
+ server.uri: https://192.168.29.232:4567
120
+ my_id: unique_id # this id is generated running mkitc init
121
+ ```
122
+
115
123
  ### Service
116
124
 
117
125
  ```
@@ -123,7 +131,12 @@ service:
123
131
  # <external_port>:[internal_port]:<tcp|http>:[round_robin (default)|leastconn]
124
132
  # to define a range on `external_port`, leave `internal_port` blank
125
133
  # - 5000-5100::tcp:round_robin
126
- # range on `internal_port` is not supported
134
+ # range on `internal_port` is not supported
135
+ # ssl suport
136
+ # <external_port>:[internal_port]:<tcp|http>:round_robin|leastconn[:ssl[,<cert.pem>(mkit.pem default)>]]
137
+ # e.g.
138
+ # - 443:80:http:round_robin:ssl # uses mkitd default crt file (mkit.pem)
139
+ # - 443:80:http:round_robin:ssl,/etc/pki/foo.pem # custom crt file full path
127
140
  - 5672:5672:tcp:round_robin
128
141
  - 80:15672:http:round_robin
129
142
  resources:
@@ -138,6 +151,72 @@ service:
138
151
  RABBITMQ_DEFAULT_VHOST: mkit
139
152
  ```
140
153
 
154
+ ## Running
155
+
156
+ The `mkitd server daemon` requires `root` user (due to `ip` and `haproxy`).
157
+ After installing the gem, server and client will be available on host.
158
+ ```
159
+ # mkitd --help
160
+ Usage: mkitd [options]
161
+ -c config-dir set the config dir (default is /etc/mkit)
162
+ -p port set the port (default is 4567)
163
+ -b bind specify bind address (e.g. 0.0.0.0)
164
+ -e env set the environment (default is development)
165
+ -o addr alias for '-b' option
166
+ --no-ssl disable ssl - use http for local server. (default is https)
167
+ --ssl-key-file PATH Path to private key (default mkit internal)
168
+ --ssl-cert-file PATH Path to certificate (default mkit internal)
169
+ ```
170
+
171
+ There's also samples for [systemd](samples/systemd) and [daemontools](samples/daemontools) as well for some miscellaneous [spps](samples/apps).
172
+
173
+ ### Accessing the API
174
+
175
+ A client is provided to interact with MKIt server.
176
+
177
+ Run `mkitc help` for a list of current supported commands.
178
+
179
+ ```
180
+ Usage: mkitc <command> [options]
181
+
182
+ Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster
183
+
184
+ Commands:
185
+
186
+ init init mkit client
187
+ ps show services status (alias for status)
188
+ status show services status
189
+ logs prints service logs
190
+ start start service
191
+ stop stop service
192
+ restart restart service
193
+ create create new service
194
+ update update service
195
+ rm remove service
196
+ version prints mkit server version
197
+ proxy haproxy status and control
198
+ profile mkit client configuration profile
199
+
200
+ Run 'mkitc help <command>' for specific command information.
201
+ ```
202
+
203
+ Example:
204
+
205
+ ```
206
+ $ mkitc ps
207
+ +----+-------+---------------+-------------------+--------------+---------+
208
+ | id | name | addr | ports | pods | status |
209
+ +----+-------+---------------+-------------------+--------------+---------+
210
+ | 1 | mongo | 10.210.198.10 | tcp/27017 | 106e2b59cb11 | RUNNING |
211
+ | 2 | nexus | 10.210.198.11 | http/80,https/443 | 68e239e5102a | RUNNING |
212
+ +----+-------+---------------+-------------------+--------------+---------+
213
+ ```
214
+ The service `mongo` is available on IP `10.210.198.10:27017`
215
+ The service `nexus` is available on IP `10.210.198.11:80` and on port `443` with ssl.
216
+
217
+ **Note:** Don't forget to call `mkitc init` to initialize client configuration and to add the `client-id`
218
+ to the server authorized clients list.
219
+
141
220
  ## Development
142
221
 
143
222
  * build the gem
data/bin/mkitc CHANGED
@@ -9,25 +9,29 @@ require 'json'
9
9
  require 'net_http_unix'
10
10
  require 'securerandom'
11
11
  require 'erb'
12
+ require 'uri'
13
+ require 'fileutils'
12
14
 
13
- class InvalidParametersException < RuntimeError
15
+ class InvalidParametersException < Exception
14
16
  attr_reader :command
17
+
15
18
  def initialize(cause, command = nil)
16
19
  super(cause)
17
20
  @command = command
18
21
  end
19
22
  end
20
23
 
21
- class MKItClient
22
- def initialize
23
- @client = NetX::HTTPUnix.new('localhost', 4567)
24
- end
25
-
26
- def dict
24
+ class CommandPalette
25
+ def schema
27
26
  global_args = [
28
27
  { short: '-v', long: '--verbose', help: 'verbose', mandatory: false, value: nil }
29
28
  ]
30
- command_dict = [
29
+ [
30
+ {
31
+ cmd: 'init',
32
+ help: 'init mkit client',
33
+ request: { }
34
+ },
31
35
  {
32
36
  cmd: 'ps',
33
37
  args: [
@@ -140,69 +144,96 @@ class MKItClient
140
144
  ],
141
145
  help: 'haproxy status and control',
142
146
  usage: ['<start|stop|restart|status>']
147
+ },
148
+ {
149
+ cmd: 'profile',
150
+ options: [
151
+ {
152
+ cmd: 'set',
153
+ request: { verb: 'set' },
154
+ args: [
155
+ { name: 'profile_name', mandatory: true }
156
+ ],
157
+ help: 'set mkit client configuration profile'
158
+ },
159
+ {
160
+ cmd: 'show',
161
+ request: { verb: 'show' },
162
+ help: 'show mkit client current profile'
163
+ }
164
+ ],
165
+ help: 'mkit client configuration profile',
166
+ usage: ['<[set <profile_name>]|[show]>']
143
167
  }
144
168
  ]
145
- command_dict
146
169
  end
170
+ end
147
171
 
148
- def help(cause: nil, cmd: nil)
149
- msg = ''
150
- if cause.nil?
151
- my_cmd = cmd
152
- else
153
- msg += "MKItc: #{cause.message}\n"
154
- my_cmd = cause.command
155
- end
156
- if my_cmd.nil?
157
- msg += "\nUsage: mkitc <command> [options]\n\n"
158
- msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n"
159
- msg += "Commands:\n\n"
160
- dict.each do |c|
161
- msg += format("%-10s %s\n", c[:cmd], c[:help])
162
- end
163
- msg += "\n"
164
- msg += "Run 'mkitc help <command>' for specific command information.\n\n"
165
- else
166
- msg += format("\nUsage: mkitc %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' '))
167
- msg += format("%s\n", my_cmd[:help])
168
- unless my_cmd[:options].nil?
169
- msg += "\nOptions:\n"
170
- my_cmd[:options].each do |c|
171
- msg += format("%-10s %s\n", c[:cmd], c[:help])
172
- end
173
- end
174
- msg += "\n"
172
+ class MKItClient
173
+ def initialize
174
+ @root = File.expand_path('..', __dir__)
175
+ @config_dir = "#{ENV['HOME']}/.mkit"
176
+ @profile_file = "#{@config_dir}/current"
177
+ @commands = CommandPalette.new
178
+ @config_file = "#{@config_dir}/mkitc_config.yml"
179
+ create_default_config
180
+ end
181
+
182
+ def create_default_config
183
+ unless File.exist?(@config_dir)
184
+ puts "Creating config directory on '#{@config_dir}'..."
185
+ FileUtils.mkdir_p(@config_dir)
175
186
  end
176
- puts msg
177
- exit 1
187
+ FileUtils.cp("#{@root}/config/mkitc_config.yml", @config_dir) unless File.exist?(@config_file)
188
+ profile({ verb: 'set' }, { profile_name: 'local' }) unless File.exist?(@profile_file)
178
189
  end
179
190
 
180
- def create(request, request_hash = nil)
181
- unless File.file?(request_hash[:file])
182
- raise InvalidParametersException.new('File not found.', c = dict.select { |k| k[:cmd] == 'create' }.first)
191
+ def read_configuration(init_call = false)
192
+ current_profile = File.read(@profile_file)
193
+ if current_profile.nil? || current_profile.empty?
194
+ # force set default
195
+ profile({ verb: 'set' }, { profile_name: 'local' })
196
+ current_profile = 'local'
183
197
  end
198
+ cfg = YAML.load_file(@config_file)
184
199
 
185
- yaml = YAML.load_file(request_hash[:file])
186
- if yaml['service'].nil?
187
- raise InvalidParametersException.new('Invalid configuration file', c = dict.select { |k| k[:cmd] == 'create' }.first)
188
- else
189
- request(request, request_hash)
200
+ if cfg['mkit'].nil? || cfg['mkit'][current_profile.lstrip].nil?
201
+ raise InvalidParametersException, "invalid configuration found on '~/.mkit' or profile not found"
190
202
  end
191
- end
192
203
 
193
- def update(request, request_hash = nil)
194
- unless File.file?(request_hash[:file])
195
- raise InvalidParametersException.new('File not found.', c = dict.select { |k| k[:cmd] == 'update' }.first)
204
+ @configuration = cfg['mkit'][current_profile.lstrip]
205
+ if !init_call && cfg['my_id'].nil?
206
+ raise InvalidParametersException.new("Please run 'mkitc init' to initialize mkit client.", find_command('init'))
196
207
  end
208
+ @my_id = cfg['my_id']
209
+ cfg
210
+ end
197
211
 
198
- yaml = YAML.load_file(request_hash[:file])
199
- if yaml['service'].nil?
200
- raise InvalidParametersException.new('Invalid configuration file', c = dict.select { |k| k[:cmd] == 'update' }.first)
212
+ def client(req)
213
+ read_configuration
214
+ req['X-API-KEY'] = @my_id
215
+ uri = URI(@configuration['server.uri'])
216
+ case uri.scheme
217
+ when 'https'
218
+ @client = NetX::HTTPUnix.new(uri.host, uri.port)
219
+ @client.use_ssl = true
220
+ @client.verify_mode = OpenSSL::SSL::VERIFY_NONE
221
+ when 'http'
222
+ @client = NetX::HTTPUnix.new(uri.host, uri.port)
223
+ when 'sock'
224
+ @client = NetX::HTTPUnix.new("unix://#{uri.path}")
201
225
  else
202
- id = yaml['service']['name']
203
- request_hash[:id] = id
204
- request(request, request_hash)
226
+ raise InvalidParametersException, 'Invalid mkit server uri. Please check configuration'
205
227
  end
228
+ @client.request(req)
229
+ end
230
+
231
+ def dict
232
+ @commands.schema
233
+ end
234
+
235
+ def find_command(cmd)
236
+ dict.select { |k| k[:cmd] == cmd }.first
206
237
  end
207
238
 
208
239
  def parse_args(args)
@@ -211,55 +242,35 @@ class MKItClient
211
242
  # short circuit for help
212
243
  if cmd == 'help' || args.empty?
213
244
  if args.size > 1
214
- c = dict.select { |k| k[:cmd] == args[1] }.first
245
+ c = find_command(args[1])
215
246
  raise InvalidParametersException, "'#{args[1]}' is not a valid help topic." if c.nil?
216
247
  end
217
248
  return help(cmd: c)
218
249
  else
219
- c = dict.select { |k| k[:cmd] == cmd }.first
250
+ c = find_command(cmd)
220
251
  end
221
252
  raise InvalidParametersException, 'Command not found' if c.nil?
222
253
 
254
+ command = c
223
255
  myargs = args.dup
224
256
  myargs.delete(cmd)
225
257
 
226
- max_args_size = c[:args].nil? ? 0 : c[:args].size
227
- max_options_size = c[:options].nil? ? 0 : 1
228
- max_args_size += max_options_size
229
-
230
- min_args_size = c[:args].nil? ? 0 : c[:args].select { |a| a[:mandatory] == true }.size
231
- min_options_size = c[:options].nil? ? 0 : 1
232
- min_args_size += min_options_size
233
-
234
- if myargs.size > max_args_size || myargs.size < min_args_size
235
- raise InvalidParametersException.new('Invalid parameters found.', c)
236
- end
237
-
238
258
  request_hash = {}
239
- request = c[:request]
259
+ request = command[:request]
240
260
  unless myargs.empty?
241
- unless c[:args].nil?
242
- idx = 0
243
- c[:args].each do |a|
244
- request_hash[a[:name].to_sym] = myargs[idx]
245
- request[:uri] = request[:uri] + a[:uri] unless a[:uri].nil?
246
- idx += 1
247
- end
248
- end
249
261
  # options
250
262
  unless c[:options].nil?
251
- option = nil
252
- myargs.each do |s|
253
- option = c[:options].select { |o| o[:cmd] == s }.first
254
- raise InvalidParametersException.new('Invalid parameters found.', c) if option.nil? || option.empty?
255
- end
256
- raise InvalidParametersException.new('Invalid parameters found.', c) if option.nil? || option.empty?
263
+ command = c[:options].select { |o| o[:cmd] == myargs[0] }.first
264
+ raise InvalidParametersException.new('Invalid parameters found.', c) if command.nil? || command.empty?
257
265
 
258
- request = option[:request]
266
+ myargs.delete_at(0)
267
+ request = command[:request]
259
268
  end
269
+ fill_cmd_args(command[:args], myargs, request, request_hash)
260
270
  end
261
- raise InvalidParametersException, "Can't find request." if request.nil?
271
+ raise InvalidParametersException.new('Invalid command or parameters.', c) if request.nil?
262
272
 
273
+ validate_command(command, request_hash)
263
274
  if respond_to? c[:cmd]
264
275
  send(c[:cmd], request, request_hash)
265
276
  else
@@ -267,11 +278,25 @@ class MKItClient
267
278
  end
268
279
  end
269
280
 
270
- def doIt(args)
271
- result = parse_args(args)
272
- puts result
273
- rescue InvalidParametersException => e
274
- help(cause: e)
281
+ def fill_cmd_args(args, myargs, request, request_hash)
282
+ return if args.nil?
283
+
284
+ idx = 0
285
+ args.each do |a|
286
+ request_hash[a[:name].to_sym] = myargs[idx]
287
+ request[:uri] = request[:uri] + a[:uri] unless a[:uri].nil?
288
+ idx += 1
289
+ end
290
+ end
291
+
292
+ def validate_command(command, request_hash)
293
+ return if command[:args].nil?
294
+
295
+ command[:args].select { |a| a[:mandatory] == true }.each do |a|
296
+ if request_hash[a[:name].to_sym].nil?
297
+ raise InvalidParametersException.new("Missing mandatory parameter: #{a[:name]}", command)
298
+ end
299
+ end
275
300
  end
276
301
 
277
302
  def request(request, request_args = nil)
@@ -304,7 +329,7 @@ class MKItClient
304
329
  when :delete
305
330
  req = Net::HTTP::Delete.new(uri)
306
331
  end
307
- @client.request(req).body
332
+ client(req).body
308
333
  end
309
334
 
310
335
  def attach(file)
@@ -318,6 +343,118 @@ class MKItClient
318
343
  body << "\r\n--#{boundary}--\r\n"
319
344
  [body.join, boundary]
320
345
  end
346
+
347
+ def doIt(args)
348
+ result = parse_args(args)
349
+ puts result
350
+ rescue InvalidParametersException => e
351
+ help(cause: e)
352
+ end
353
+
354
+ def help(cause: nil, cmd: nil)
355
+ msg = ''
356
+ if cause.nil?
357
+ my_cmd = cmd
358
+ else
359
+ msg += "MKItc: #{cause.message}\n"
360
+ my_cmd = cause.command
361
+ end
362
+ if my_cmd.nil?
363
+ msg += "\nUsage: mkitc <command> [options]\n\n"
364
+ msg += "Micro k8s on Ruby - a simple tool to mimic a (very) minimalistic k8 cluster\n\n"
365
+ msg += "Commands:\n\n"
366
+ dict.each do |c|
367
+ msg += format("%-10s %s\n", c[:cmd], c[:help])
368
+ end
369
+ msg += "\n"
370
+ msg += "Run 'mkitc help <command>' for specific command information.\n\n"
371
+ else
372
+ msg += format("\nUsage: mkitc %s %s\n\n", my_cmd[:cmd], my_cmd[:usage].nil? ? '' : my_cmd[:usage].join(' '))
373
+ msg += format("%s\n", my_cmd[:help])
374
+ unless my_cmd[:options].nil?
375
+ msg += "\nOptions:\n"
376
+ my_cmd[:options].each do |c|
377
+ msg += format("%-10s %s\n", c[:cmd], c[:help])
378
+ end
379
+ end
380
+ msg += "\n"
381
+ end
382
+ puts msg
383
+ exit 1
384
+ end
385
+
386
+ def init(request, request_hash = nil)
387
+ cfg = read_configuration(true)
388
+ if cfg['my_id'].nil?
389
+ my_id = SecureRandom.uuid.gsub('-','')
390
+ cfg['my_id'] = my_id
391
+ File.write(@config_file, cfg.to_yaml)
392
+ puts "Please check if your api-key is on mkitd server allowed keys"
393
+ else
394
+ my_id = cfg['my_id']
395
+ end
396
+ puts "Your api-key is #{my_id}"
397
+ end
398
+ def create(request, request_hash = nil)
399
+ unless File.file?(request_hash[:file])
400
+ raise InvalidParametersException.new('File not found.', find_command('create'))
401
+ end
402
+
403
+ yaml = YAML.load_file(request_hash[:file])
404
+ if yaml['service'].nil?
405
+ raise InvalidParametersException.new('Invalid configuration file', find_command('create'))
406
+ else
407
+ request(request, request_hash)
408
+ end
409
+ end
410
+
411
+ def update(request, request_hash = nil)
412
+ unless File.file?(request_hash[:file])
413
+ raise InvalidParametersException.new('File not found.', find_command('update'))
414
+ end
415
+
416
+ yaml = YAML.load_file(request_hash[:file])
417
+ if yaml['service'].nil?
418
+ raise InvalidParametersException.new('Invalid configuration file', find_command('update'))
419
+ else
420
+ id = yaml['service']['name']
421
+ request_hash[:id] = id
422
+ request(request, request_hash)
423
+ end
424
+ end
425
+
426
+ def profile(request, request_hash = {})
427
+ cfg = YAML.load_file("#{@config_dir}/mkitc_config.yml")
428
+ cmd = find_command('profile')
429
+ if cfg['mkit'].nil?
430
+ raise InvalidParametersException.new(
431
+ "Invalid configuration on '~/.mkit'\nPlease fix or clean up for defaults apply", cmd
432
+ )
433
+ end
434
+
435
+ case request[:verb]
436
+ when 'set'
437
+ profile = request_hash[:profile_name]
438
+ if cfg['mkit'][profile.lstrip].nil?
439
+ raise InvalidParametersException.new("Profile not found on '~/.mkit' configuration", cmd)
440
+ end
441
+
442
+ puts "Setting current profile to #{profile}."
443
+ File.write(@profile_file, request_hash[:profile_name])
444
+ ''
445
+ when 'show'
446
+ active = File.read("#{@config_dir}/current")
447
+ cfg['mkit'].map do |k, _v|
448
+ if k == active
449
+ "*#{k}"
450
+ else
451
+ k
452
+ end
453
+ end.join(' ')
454
+ else
455
+ raise InvalidParametersException.new("Invalid 'profile' operation", cmd)
456
+ end
457
+ end
321
458
  end
322
459
 
323
460
  #
data/bin/mkitd CHANGED
@@ -9,9 +9,7 @@ def up
9
9
  require 'sinatra'
10
10
  require 'sinatra/base'
11
11
  require 'mkit'
12
- # defaults
13
- PARAMS_CONFIG[:bind] ||= 'localhost'
14
- PARAMS_CONFIG[:port] ||= 4567
12
+
15
13
  MKIt.startup(options: PARAMS_CONFIG)
16
14
 
17
15
  use Rack::MethodOverride
@@ -21,26 +19,27 @@ def up
21
19
 
22
20
  # sinatra::base ignores in parameters
23
21
  # set it here or via configure...
24
- Sinatra::Application.run!({ port: PARAMS_CONFIG[:port], bind: PARAMS_CONFIG[:bind] })
25
- # MKIt::Server.run
22
+ # Sinatra::Application.run!({ port: PARAMS_CONFIG[:port], bind: PARAMS_CONFIG[:bind] })
23
+ Sinatra::Application.run! do |server|
24
+ MKIt.options(server)
25
+ end
26
+
26
27
  end
27
28
 
28
29
  if ARGV.any?
29
30
  require 'optparse'
30
31
  parser = OptionParser.new do |op|
31
32
  op.on('-c config-dir', 'set the config dir (default is /etc/mkit)') { |val| PARAMS_CONFIG[:config_dir] = val }
32
- op.on('-p port', 'set the port (default is 4567)') { |val| PARAMS_CONFIG[:port] = Integer(val) }
33
- op.on('-b bind ', 'specify bind address (e.g. /tmp/app.sock)') { |val| PARAMS_CONFIG[:bind] = val }
34
- op.on('-s server', 'specify rack server/handler') { |val| PARAMS_CONFIG[:server] = val }
35
- op.on('-q', 'turn on quiet mode (default is off)') { PARAMS_CONFIG[:quiet] = true }
36
- op.on('-x', 'turn on the mutex lock (default is off)') { PARAMS_CONFIG[:lock] = true }
37
- op.on('-e env', 'set the environment (default is development)') do |val|
33
+ op.on('-p port', 'set the port (default is 4567)') { |val| PARAMS_CONFIG[:port] = Integer(val) }
34
+ op.on('-b bind', 'specify bind address (e.g. 0.0.0.0)') { |val| PARAMS_CONFIG[:bind] = val }
35
+ op.on('-o addr', 'alias for bind option') { |val| PARAMS_CONFIG[:bind] = val }
36
+ op.on('-e env', 'set the environment (default is development)') do |val|
38
37
  ENV['RACK_ENV'] = val
39
38
  PARAMS_CONFIG[:environment] = val.to_sym
40
39
  end
41
- op.on('-o addr', "set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))") do |val|
42
- PARAMS_CONFIG[:bind] = val
43
- end
40
+ op.on('--no-ssl', 'disable ssl - use http for local server. (default is https)') { PARAMS_CONFIG[:ssl] = false }
41
+ op.on('--ssl-key-file PATH', 'Path to private key (default mkit internal)') { |val| PARAMS_CONFIG[:private_key_file] = val }
42
+ op.on('--ssl-cert-file PATH', 'Path to certificate (default mkit internal)') { |val| PARAMS_CONFIG[:cert_chain_file] = val }
44
43
  end
45
44
  begin
46
45
  parser.parse!(ARGV.dup)
@@ -1,3 +1,4 @@
1
+ ---
1
2
  mkit:
2
3
  my_network:
3
4
  ip: 10.210.198.1
@@ -12,4 +13,5 @@ mkit:
12
13
  bin: /usr/sbin/haproxy
13
14
  database:
14
15
  env: development
15
-
16
+ clients:
17
+ - add_client_id: add client's id to allow access to this server
@@ -0,0 +1,3 @@
1
+ mkit:
2
+ local:
3
+ server.uri: https://localhost:4567
@@ -1,5 +1,5 @@
1
1
  #
2
- # kidsd server options (for systemd unit | daemontools)
2
+ # mkitd server options (for systemd unit | daemontools)
3
3
  #
4
4
  OPTIONS=""
5
5
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ServicePortsSsl< ActiveRecord::Migration[5.1]
4
+ def up
5
+ add_column :service_ports, :ssl, :string
6
+ add_column :service_ports, :crt, :string
7
+ end
8
+ end
@@ -24,9 +24,28 @@ module MKIt
24
24
  end
25
25
 
26
26
  def build_table_row(data)
27
- ports = data.service_port&.each.map { |p| "#{p.mode}/#{p.external_port}" }.join(',')
27
+ ports = data.service_port&.each.map { |p| build_port(p) }.join(',')
28
28
  pods = data.pod.each.map { |p| p.name.to_s }.join(' ')
29
29
  [data.id, data.name, data.lease&.ip, ports, pods, data.status]
30
30
  end
31
+
32
+ def build_port(p)
33
+ case p.mode
34
+ when 'http'
35
+ if p.ssl?
36
+ "#{p.mode}s/#{p.external_port}"
37
+ else
38
+ "#{p.mode}/#{p.external_port}"
39
+ end
40
+ when 'tcp'
41
+ if p.ssl?
42
+ "s#{p.mode}/#{p.external_port}"
43
+ else
44
+ "#{p.mode}/#{p.external_port}"
45
+ end
46
+ else
47
+ "#{p.mode}/#{p.external_port}"
48
+ end
49
+ end
31
50
  end
32
51
  end
@@ -9,6 +9,14 @@ module MKIt
9
9
  set :show_exceptions, false
10
10
  set :raise_errors, false
11
11
 
12
+ before do
13
+ api_key = request.env['HTTP_X_API_KEY']
14
+ cfg = YAML.load_file(MKIt::Config.config_file)
15
+ if cfg.nil? || cfg['mkit'].nil? || cfg['mkit']['clients'].nil? || !cfg['mkit']['clients'].map{|h| h['id']}.include?(api_key)
16
+ error 401, 'Unauthorized - please add your client-id to authorized clients list'
17
+ end
18
+ end
19
+
12
20
  error MKIt::BaseException do |e|
13
21
  MKItLogger.debug e
14
22
  error e.error_code, e.message
@@ -4,7 +4,7 @@ require 'mkit/exceptions'
4
4
  class ServicePort < ActiveRecord::Base
5
5
  belongs_to :service
6
6
 
7
- CONFIG_EXPRESSION=/^(.*?):(.*?):(tcp|http):(.*?)$/
7
+ CONFIG_EXPRESSION=/^(.*?):(.*?):(tcp|http):(.*?)($|:ssl$|:ssl,(.+))$/
8
8
 
9
9
  def self.create(service:, config:)
10
10
  sp = ServicePort.new(service: service, version: service.version)
@@ -18,22 +18,34 @@ class ServicePort < ActiveRecord::Base
18
18
  # # src:dest:tcp|http:round_robin|leastconn
19
19
  # - 5532:5432:tcp:round_robin
20
20
  # - 5532-6000::tcp:round_robin
21
+ # # ssl support:
22
+ # # src:dest:tcp|http:round_robin|leastconn[:ssl[,<cert.pem>(mkit.pem default)>]]
23
+ # - 443:80:tcp:round_robin:ssl # crt file is mkit.pem
24
+ # - 443:80:tcp:round_robin:ssl,/etc/pki/foo.pem # crt file full path
21
25
  # model:
22
26
  # service_ports:
23
27
  # - external: 5432
24
28
  # internal: 5432
25
29
  # mode: tcp|http
26
30
  # load_bal: round_robin
31
+ # ssl: true|false
32
+ # crt: full path
27
33
  def parse_config(config)
28
34
  ports = config.match(CONFIG_EXPRESSION)
29
35
  raise MKIt::InvalidPortsConfiguration.new("no match with config expression $#{CONFIG_EXPRESSION}") if ports.nil?
30
36
 
37
+ puts ports
31
38
  self.external_port = ports[1]
32
39
  self.internal_port = ports[2]
33
40
  self.mode = ports[3]
34
41
  self.load_bal = ports[4]
42
+ self.ssl = !ports[5].nil? && ports[5].start_with?(':ssl') ? 'true':'false'
43
+ self.crt = ports[7].nil? ? MKIt::Utils.proxy_cert : ports[7]
35
44
  end
36
45
 
46
+ def ssl?
47
+ self.ssl == 'true'
48
+ end
37
49
  def load_balance
38
50
  case self.load_bal
39
51
  when /^round_robin$/
@@ -6,7 +6,7 @@
6
6
  # start <%=name%>-<%=port.external_port%>
7
7
  #
8
8
  frontend <%=name%>-<%=port.external_port%>-front
9
- bind <%=lease.ip%>:<%=port.external_port%>
9
+ bind <%=lease.ip%>:<%=port.external_port%> <%=if port.ssl? then "ssl crt #{port.crt}" end%>
10
10
  mode <%=port.mode%>
11
11
  #
12
12
  use_backend <%=name%>-<%=port.external_port%>-back
@@ -6,13 +6,18 @@ require 'yaml'
6
6
  module MKIt
7
7
  module Config
8
8
  extend self
9
+
9
10
  def load_yml!(path)
11
+ @config_file = path
10
12
  @config = YAML.load(File.new(path).read).to_o
11
13
  end
14
+
15
+ def config_file
16
+ @config_file
17
+ end
12
18
  #
13
19
  def method_missing(name,*args)
14
20
  return @config.send(name,*args)
15
- super.method_missing name
16
21
  end
17
22
  end
18
23
  end
@@ -0,0 +1,28 @@
1
+ require 'openssl'
2
+
3
+ module MKIt
4
+ class EasySSL
5
+ def self.create_self_certificate(cert_dir)
6
+ unless File.exist?("#{cert_dir}/#{MKIt::Utils::MKIT_CRT}")
7
+ key = OpenSSL::PKey::RSA.new 4096
8
+ name = OpenSSL::X509::Name.parse '/CN=MKIt/DC=server'
9
+ cert = OpenSSL::X509::Certificate.new
10
+ cert.version = 2
11
+ cert.serial = 0
12
+ cert.not_before = Time.now
13
+ cert.not_after = Time.now + 20 * 365 * 24 * 60 * 60
14
+ cert.public_key = key.public_key
15
+ cert.subject = name
16
+ cert.issuer = name
17
+ cert.sign key, OpenSSL::Digest.new('SHA256')
18
+ # my cert and key files
19
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_CRT}", 'w' do |io| io.write cert.to_pem end
20
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_KEY}", 'w' do |io| io.write key.to_pem end
21
+ # haproxy default ssl cert
22
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_PEM}", 'w' do |io| io.write cert.to_pem end
23
+ open "#{cert_dir}/#{MKIt::Utils::MKIT_PEM}", 'a' do |io| io.write key.to_pem end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
data/lib/mkit/utils.rb CHANGED
@@ -4,6 +4,10 @@ module MKIt
4
4
  module Utils
5
5
  module_function
6
6
 
7
+ MKIT_CRT = 'mkit.crt'
8
+ MKIT_KEY = 'mkit.key'
9
+ MKIT_PEM = 'mkit.pem'
10
+
7
11
  def me
8
12
  'mkit'
9
13
  end
@@ -24,6 +28,10 @@ module MKIt
24
28
  @config_dir.nil? ? "#{self.root}/config" : @config_dir
25
29
  end
26
30
 
31
+ def proxy_cert
32
+ "#{config_dir}/#{MKIT_PEM}"
33
+ end
34
+
27
35
  def load_db_config(db_config_dir = self.config_dir)
28
36
  self.log.info "loading database configurations from '#{config_dir}'..."
29
37
  YAML::load(ERB.new(IO.read("#{db_config_dir}/database.yml")).result)
data/lib/mkit/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module MKIt
2
- VERSION = "0.4.3"
2
+ VERSION = "0.6.0"
3
3
  end
4
4
 
data/lib/mkit.rb CHANGED
@@ -29,6 +29,7 @@ require 'mkit/docker_listener'
29
29
  require 'mkit/app/helpers/haproxy'
30
30
  require 'active_record/tasks/database_tasks'
31
31
  require 'mkit/utils'
32
+ require 'mkit/ssl/easy_ssl'
32
33
 
33
34
  MKItLogger = Console.logger
34
35
 
@@ -40,15 +41,24 @@ module MKIt
40
41
 
41
42
  def self.configure(options:)
42
43
  @root = MKIt::Utils.root
44
+ @options = options
43
45
  MKItLogger.debug!
44
46
  #
45
47
  # config dir
46
48
  @config_dir = if ENV['RACK_ENV'] != 'development'
47
- options[:config_dir].nil? ? '/etc/mkit' : options[:config_dir]
49
+ @options[:config_dir].nil? ? '/etc/mkit' : @options[:config_dir]
48
50
  else
49
- options[:config_dir].nil? ? "#{@root}/config" : options[:config_dir]
51
+ @options[:config_dir].nil? ? "#{@root}/config" : @options[:config_dir]
50
52
  end
51
53
  MKIt::Utils.set_config_dir(@config_dir)
54
+ # defaults
55
+ @bind = options[:bind] ||= 'localhost'
56
+ @port = options[:port] ||= 4567
57
+ @ssl = options[:ssl].nil? ? true : options[:ssl] && true
58
+ @verify_peer = options[:verify_peer].nil? ? false : options[:verify_peer] && true
59
+ @cert_chain_file = options[:cert_chain_file] ||= "#{@config_dir}/#{MKIt::Utils::MKIT_CRT}"
60
+ @private_key_file = options[:private_key_file] ||= "#{@config_dir}/#{MKIt::Utils::MKIT_KEY}"
61
+
52
62
  # create dirs
53
63
  if ENV['RACK_ENV'] != 'development' || !options[:config_dir].nil?
54
64
  check_config_files = false
@@ -65,9 +75,11 @@ module MKIt
65
75
  exit
66
76
  end
67
77
  end
68
- #
78
+
69
79
  # load configuration
70
80
  MKIt::Initializers.load_my_configuration
81
+ # cert
82
+ MKIt::EasySSL.create_self_certificate(@config_dir)
71
83
  #
72
84
  # run config based tasks
73
85
  FileUtils.mkdir_p(MKIt::Config.mkit.haproxy.config_dir)
@@ -92,6 +104,20 @@ module MKIt
92
104
  DatabaseTasks.root = @root
93
105
  end
94
106
 
107
+ def self.options(server)
108
+ if @ssl
109
+ ssl_options = {
110
+ private_key_file: @private_key_file,
111
+ cert_chain_file: @cert_chain_file,
112
+ verify_peer: @verify_peer
113
+ }
114
+ server.ssl = true
115
+ server.ssl_options = ssl_options
116
+ end
117
+ server.backend.port = @port
118
+ server.backend.host = @bind
119
+ end
120
+
95
121
  def self.establish_db_connection
96
122
  ActiveRecord::Base.establish_connection(DatabaseTasks.database_configuration[DatabaseTasks.env])
97
123
  ActiveRecord::Base.connection.migration_context.migrations_paths.clear
@@ -5,6 +5,7 @@ service:
5
5
  network: bridge
6
6
  ports:
7
7
  - 80:8081:http:round_robin
8
+ - 443:8081:http:round_robin:ssl
8
9
  resources:
9
10
  max_replicas: 1
10
11
  min_replicas: 1
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasco Santos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-12 00:00:00.000000000 Z
11
+ date: 2024-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-dns
@@ -311,9 +311,11 @@ files:
311
311
  - bin/mkitd
312
312
  - config/database.yml
313
313
  - config/mkit_config.yml
314
+ - config/mkitc_config.yml
314
315
  - config/mkitd_config.sh
315
316
  - db/migrate/001_setup.rb
316
317
  - db/migrate/002_mkit_jobs.rb
318
+ - db/migrate/003_service_ports_ssl.rb
317
319
  - db/schema.rb
318
320
  - lib/mkit.rb
319
321
  - lib/mkit/app/controllers/mkit_controller.rb
@@ -349,13 +351,13 @@ files:
349
351
  - lib/mkit/ctypes.rb
350
352
  - lib/mkit/docker_listener.rb
351
353
  - lib/mkit/exceptions.rb
352
- - lib/mkit/haproxy.rb
353
354
  - lib/mkit/job_manager.rb
354
355
  - lib/mkit/mkit_dns.rb
355
356
  - lib/mkit/mkit_interface.rb
356
357
  - lib/mkit/sagas/asaga.rb
357
358
  - lib/mkit/sagas/create_pod_saga.rb
358
359
  - lib/mkit/sagas/saga_manager.rb
360
+ - lib/mkit/ssl/easy_ssl.rb
359
361
  - lib/mkit/status.rb
360
362
  - lib/mkit/utils.rb
361
363
  - lib/mkit/version.rb
data/lib/mkit/haproxy.rb DELETED
@@ -1,48 +0,0 @@
1
- require 'pty'
2
- #
3
- #
4
- #
5
- module MKIt
6
- class HAProxy
7
-
8
- def initialize
9
- # configs
10
- # run standalone | daemon
11
- @running = false
12
- end
13
-
14
- def start
15
- @thread ||= Thread.new {
16
- while (@running) do
17
- cmd = "/usr/sbin/haproxy -f /etc/haproxy/haproxy.d"
18
- %x{#{cmd}}
19
- sleep 1
20
- end
21
- }
22
- @thread.run
23
- puts "haproxy started"
24
- end
25
-
26
- def start
27
- @running = true
28
- @thread ||= Thread.new {
29
- while (@running) do
30
- %{/usr/sbin/haproxy -f /etc/haproxy/haproxy.d/}
31
- sleep(1)
32
- end
33
- }
34
- puts "proxy started"
35
- end
36
-
37
- def stop
38
- puts "proxy stopped"
39
- end
40
-
41
- def status
42
- end
43
-
44
- def reload
45
- end
46
- end
47
- end
48
-