kuby-core 0.7.2 → 0.8.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/kuby-core.gemspec +5 -3
  4. data/lib/kuby.rb +5 -3
  5. data/lib/kuby/definition.rb +0 -8
  6. data/lib/kuby/docker/layer.rb +4 -4
  7. data/lib/kuby/docker/metadata.rb +6 -6
  8. data/lib/kuby/docker/package_phase.rb +2 -2
  9. data/lib/kuby/docker/spec.rb +11 -11
  10. data/lib/kuby/environment.rb +6 -2
  11. data/lib/kuby/kubernetes.rb +0 -2
  12. data/lib/kuby/kubernetes/deploy_task.rb +0 -1
  13. data/lib/kuby/kubernetes/deployer.rb +7 -7
  14. data/lib/kuby/kubernetes/minikube_provider.rb +4 -0
  15. data/lib/kuby/kubernetes/provider.rb +5 -5
  16. data/lib/kuby/kubernetes/spec.rb +8 -8
  17. data/lib/kuby/plugin.rb +59 -0
  18. data/lib/kuby/plugins.rb +6 -0
  19. data/lib/kuby/plugins/nginx_ingress.rb +71 -0
  20. data/lib/kuby/plugins/rails_app.rb +18 -0
  21. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +117 -0
  22. data/lib/kuby/plugins/rails_app/assets.rb +347 -0
  23. data/lib/kuby/plugins/rails_app/database.rb +75 -0
  24. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/generators/kuby.rb +0 -0
  25. data/lib/kuby/plugins/rails_app/mysql.rb +155 -0
  26. data/lib/kuby/plugins/rails_app/plugin.rb +398 -0
  27. data/lib/kuby/plugins/rails_app/postgres.rb +143 -0
  28. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +11 -0
  29. data/lib/kuby/plugins/rails_app/sqlite.rb +32 -0
  30. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/tasks.rake +10 -2
  31. data/lib/kuby/tasks.rb +9 -9
  32. data/lib/kuby/tasks/kuby.rake +1 -1
  33. data/lib/kuby/version.rb +1 -1
  34. metadata +34 -27
  35. data/lib/ext/krane/kubernetes_resource.rb +0 -16
  36. data/lib/kuby/kubernetes/plugin.rb +0 -55
  37. data/lib/kuby/kubernetes/plugins.rb +0 -8
  38. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +0 -73
  39. data/lib/kuby/kubernetes/plugins/rails_app.rb +0 -16
  40. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +0 -79
  41. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +0 -154
  42. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +0 -379
  43. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +0 -142
  44. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +0 -13
  45. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +0 -30
@@ -0,0 +1,75 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module Kuby
5
+ module Plugins
6
+ module RailsApp
7
+ class UnsupportedDatabaseError < StandardError; end
8
+
9
+ class Database
10
+ ADAPTER_MAP = {
11
+ sqlite3: Sqlite,
12
+ mysql2: MySQL,
13
+ postgresql: Postgres
14
+ }.freeze
15
+
16
+ def self.get(rails_app)
17
+ if rails_app.manage_database?
18
+ new(rails_app)
19
+ end
20
+ end
21
+
22
+ def self.get_adapter(adapter_name)
23
+ ADAPTER_MAP.fetch(adapter_name) do
24
+ raise UnsupportedDatabaseError, "Kuby does not support the '#{adapter}' "\
25
+ 'database adapter'
26
+ end
27
+ end
28
+
29
+ attr_reader :rails_app
30
+
31
+ def initialize(rails_app)
32
+ @rails_app = rails_app
33
+ end
34
+
35
+ def plugin
36
+ @plugin ||= self.class
37
+ .get_adapter(adapter_name)
38
+ .new(rails_app.environment, db_configs)
39
+ end
40
+
41
+ def adapter_name
42
+ @adapter_name ||= db_config['adapter'].to_sym
43
+ end
44
+
45
+ alias_method :plugin_name, :adapter_name
46
+
47
+ private
48
+
49
+ def db_config
50
+ @db_config ||= db_configs[rails_app.environment.name]
51
+ end
52
+
53
+ def db_configs
54
+ @db_configs ||= YAML.load(ERB.new(File.read(db_config_path)).result)
55
+ end
56
+
57
+ def db_config_path
58
+ @db_config_path ||= begin
59
+ db_config_paths.first or
60
+ raise "Couldn't find database config at #{rails_app.root}"
61
+ end
62
+ end
63
+
64
+ def db_config_paths
65
+ @db_config_paths ||=
66
+ Dir.glob(
67
+ File.join(
68
+ rails_app.root, 'config', 'database.{yml,erb,yml.erb,yaml,yaml.erb}'
69
+ )
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,155 @@
1
+ require 'kube-dsl'
2
+ require 'kuby/kube-db'
3
+
4
+ module Kuby
5
+ module Plugins
6
+ module RailsApp
7
+ class MySQL < ::Kuby::Plugin
8
+ ROLE = 'web'.freeze
9
+
10
+ attr_reader :environment, :configs
11
+
12
+ def initialize(environment, configs)
13
+ @environment = environment
14
+ @configs = configs
15
+
16
+ user(config['username'])
17
+ password(config['password'])
18
+ end
19
+
20
+ def name
21
+ :mysql
22
+ end
23
+
24
+ def resources
25
+ @resources ||= [secret, database]
26
+ end
27
+
28
+ def after_configuration
29
+ environment.docker.package_phase.add(:mysql_dev)
30
+ environment.docker.package_phase.add(:mysql_client)
31
+ end
32
+
33
+ def host
34
+ # host is the same as the name thanks to k8s DNS
35
+ @host ||= database.metadata.name
36
+ end
37
+
38
+ def rewritten_configs
39
+ # deep dup
40
+ @rewritten_configs ||= Marshal.load(Marshal.dump(configs)).tap do |new_configs|
41
+ new_configs[environment.name]['host'] = host
42
+ end
43
+ end
44
+
45
+ def user(user)
46
+ secret do
47
+ data do
48
+ set :user, user
49
+ end
50
+ end
51
+ end
52
+
53
+ def password(password)
54
+ secret do
55
+ data do
56
+ set :password, password
57
+ end
58
+ end
59
+ end
60
+
61
+ def storage(amount)
62
+ database do
63
+ spec do
64
+ storage do
65
+ resources do
66
+ requests do
67
+ set :storage, amount
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def secret(&block)
76
+ context = self
77
+
78
+ @secret ||= KubeDSL.secret do
79
+ metadata do
80
+ name "#{context.base_name}-mysql-secret"
81
+ namespace context.kubernetes.namespace.metadata.name
82
+ end
83
+
84
+ type 'Opaque'
85
+ end
86
+
87
+ @secret.instance_eval(&block) if block
88
+ @secret
89
+ end
90
+
91
+ def database(&block)
92
+ context = self
93
+
94
+ @database ||= Kuby::KubeDB.my_sql do
95
+ api_version 'kubedb.com/v1alpha1'
96
+
97
+ metadata do
98
+ name "#{context.base_name}-mysql"
99
+ namespace context.kubernetes.namespace.metadata.name
100
+ end
101
+
102
+ spec do
103
+ database_secret do
104
+ secret_name context.secret.metadata.name
105
+ end
106
+
107
+ version '5.7-v2'
108
+ storage_type 'Durable'
109
+
110
+ storage do
111
+ storage_class_name context.kubernetes.provider.storage_class_name
112
+ access_modes ['ReadWriteOnce']
113
+
114
+ resources do
115
+ requests do
116
+ add :storage, '10Gi'
117
+ end
118
+ end
119
+ end
120
+
121
+ termination_policy 'DoNotTerminate'
122
+ end
123
+ end
124
+
125
+ @database.instance_eval(&block) if block
126
+ @database
127
+ end
128
+
129
+ def base_name
130
+ @base_name ||= "#{kubernetes.selector_app}-#{ROLE}"
131
+ end
132
+
133
+ def kubernetes
134
+ environment.kubernetes
135
+ end
136
+
137
+ private
138
+
139
+ def config
140
+ configs[environment.name]
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ Kuby.register_package(:mysql_client,
148
+ debian: 'default-mysql-client',
149
+ alpine: 'mariadb-client'
150
+ )
151
+
152
+ Kuby.register_package(:mysql_dev,
153
+ debian: 'default-libmysqlclient-dev',
154
+ alpine: 'mariadb-dev'
155
+ )
@@ -0,0 +1,398 @@
1
+ require 'kube-dsl'
2
+ require 'kuby/cert-manager'
3
+
4
+ module Kuby
5
+ module Plugins
6
+ module RailsApp
7
+ class Plugin < ::Kuby::Plugin
8
+ extend ::KubeDSL::ValueFields
9
+
10
+ WEB_ROLE = 'web'.freeze
11
+ DEFAULT_HOSTNAME = 'localhost'.freeze
12
+ MASTER_KEY_VAR = 'RAILS_MASTER_KEY'.freeze
13
+ ENV_SECRETS = [MASTER_KEY_VAR].freeze
14
+ ENV_EXCLUDE = ['RAILS_ENV'].freeze
15
+ DEFAULT_ASSET_URL = '/assets'.freeze
16
+ DEFAULT_PACKS_URL = '/packs'.freeze
17
+ DEFAULT_ASSET_PATH = './public'.freeze
18
+
19
+ value_field :root, default: '.'
20
+ value_fields :hostname, :tls_enabled
21
+ value_fields :manage_database, :database, :replicas
22
+ value_fields :asset_url, :packs_url, :asset_path
23
+
24
+ alias_method :manage_database?, :manage_database
25
+
26
+ def initialize(environment)
27
+ @environment = environment
28
+ @tls_enabled = true
29
+ @replicas = 1
30
+ @manage_database = true
31
+ @hostname = DEFAULT_HOSTNAME
32
+ @asset_url = DEFAULT_ASSET_URL
33
+ @packs_url = DEFAULT_PACKS_URL
34
+ @asset_path = DEFAULT_ASSET_PATH
35
+ end
36
+
37
+ def configure(&block)
38
+ instance_eval(&block) if block
39
+ end
40
+
41
+ def after_configuration
42
+ context = self
43
+
44
+ if @database = Database.get(self)
45
+ environment.kubernetes.plugins[database.plugin_name] = @database.plugin
46
+ environment.kubernetes.add_plugin(:kube_db)
47
+
48
+ environment.docker do
49
+ insert :rewrite_db_config, RewriteDbConfig.new, after: :copy_phase
50
+ end
51
+ end
52
+
53
+ # do we always want this?
54
+ environment.kubernetes.add_plugin(:nginx_ingress)
55
+ environment.kubernetes.add_plugin(:rails_assets) do
56
+ asset_url context.asset_url
57
+ packs_url context.packs_url
58
+ asset_path context.asset_path
59
+ end
60
+
61
+ if @tls_enabled
62
+ context = self
63
+
64
+ environment.kubernetes.add_plugin(:cert_manager) do
65
+ email context.environment.docker.credentials.email
66
+ end
67
+ end
68
+ end
69
+
70
+ def before_deploy(manifest)
71
+ # Make sure plugin has been configured. If not, do nothing.
72
+ if cert_manager = environment.kubernetes.plugin(:cert_manager)
73
+ cert_manager.annotate_ingress(ingress)
74
+ end
75
+
76
+ image_with_tag = "#{docker.metadata.image_url}:#{kubernetes.tag}"
77
+
78
+ if assets = environment.kubernetes.plugin(:rails_assets)
79
+ assets.configure_ingress(ingress, hostname)
80
+ assets.configure_deployment(deployment, image_with_tag)
81
+ end
82
+
83
+ deployment do
84
+ spec do
85
+ template do
86
+ spec do
87
+ container(:web) do
88
+ image image_with_tag
89
+ end
90
+
91
+ init_container(:create_db) do
92
+ image image_with_tag
93
+ end
94
+
95
+ init_container(:migrate_db) do
96
+ image image_with_tag
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ def database(&block)
105
+ @database.instance_eval(&block) if block
106
+ @database
107
+ end
108
+
109
+ def service(&block)
110
+ spec = self
111
+
112
+ @service ||= KubeDSL.service do
113
+ metadata do
114
+ name "#{spec.selector_app}-svc"
115
+ namespace spec.namespace.metadata.name
116
+
117
+ labels do
118
+ add :app, spec.selector_app
119
+ add :role, spec.role
120
+ end
121
+ end
122
+
123
+ spec do
124
+ type 'NodePort'
125
+
126
+ selector do
127
+ add :app, spec.selector_app
128
+ add :role, spec.role
129
+ end
130
+
131
+ port do
132
+ name 'http'
133
+ port 8080
134
+ protocol 'TCP'
135
+ target_port 'http'
136
+ end
137
+ end
138
+ end
139
+
140
+ @service.instance_eval(&block) if block
141
+ @service
142
+ end
143
+
144
+ def service_account(&block)
145
+ spec = self
146
+
147
+ @service_account ||= KubeDSL.service_account do
148
+ metadata do
149
+ name "#{spec.selector_app}-sa"
150
+ namespace spec.namespace.metadata.name
151
+
152
+ labels do
153
+ add :app, spec.selector_app
154
+ add :role, spec.role
155
+ end
156
+ end
157
+ end
158
+
159
+ @service_account.instance_eval(&block) if block
160
+ @service_account
161
+ end
162
+
163
+ def config_map(&block)
164
+ spec = self
165
+
166
+ @config_map ||= KubeDSL.config_map do
167
+ metadata do
168
+ name "#{spec.selector_app}-config"
169
+ namespace spec.namespace.metadata.name
170
+ end
171
+
172
+ data do
173
+ ENV.each_pair do |key, val|
174
+ include_key = key.start_with?('RAILS_') &&
175
+ !ENV_SECRETS.include?(key) &&
176
+ !ENV_EXCLUDE.include?(key)
177
+
178
+ if include_key
179
+ add key.to_sym, val
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ @config_map.instance_eval(&block) if block
186
+ @config_map
187
+ end
188
+
189
+ def app_secrets(&block)
190
+ spec = self
191
+
192
+ @app_secrets ||= KubeDSL.secret do
193
+ metadata do
194
+ name "#{spec.selector_app}-secrets"
195
+ namespace spec.namespace.metadata.name
196
+ end
197
+
198
+ type 'Opaque'
199
+
200
+ data do
201
+ if master_key = ENV[MASTER_KEY_VAR]
202
+ add MASTER_KEY_VAR.to_sym, master_key
203
+ else
204
+ master_key_path = File.join(spec.root, 'config', 'master.key')
205
+
206
+ if File.exist?(master_key_path)
207
+ add MASTER_KEY_VAR.to_sym, File.read(master_key_path).strip
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ @app_secrets.instance_eval(&block) if block
214
+ @app_secrets
215
+ end
216
+
217
+ def deployment(&block)
218
+ kube_spec = self
219
+
220
+ @deployment ||= KubeDSL.deployment do
221
+ metadata do
222
+ name "#{kube_spec.selector_app}-#{kube_spec.role}"
223
+ namespace kube_spec.namespace.metadata.name
224
+
225
+ labels do
226
+ add :app, kube_spec.selector_app
227
+ add :role, kube_spec.role
228
+ end
229
+ end
230
+
231
+ spec do
232
+ replicas kube_spec.replicas
233
+
234
+ selector do
235
+ match_labels do
236
+ add :app, kube_spec.selector_app
237
+ add :role, kube_spec.role
238
+ end
239
+ end
240
+
241
+ strategy do
242
+ type 'RollingUpdate'
243
+
244
+ rolling_update do
245
+ max_surge '25%'
246
+ max_unavailable 0
247
+ end
248
+ end
249
+
250
+ template do
251
+ metadata do
252
+ labels do
253
+ add :app, kube_spec.selector_app
254
+ add :role, kube_spec.role
255
+ end
256
+ end
257
+
258
+ spec do
259
+ container(:web) do
260
+ name "#{kube_spec.selector_app}-#{kube_spec.role}"
261
+ image_pull_policy 'IfNotPresent'
262
+
263
+ port do
264
+ container_port kube_spec.docker.webserver_phase.port
265
+ name 'http'
266
+ protocol 'TCP'
267
+ end
268
+
269
+ env_from do
270
+ config_map_ref do
271
+ name kube_spec.config_map.metadata.name
272
+ end
273
+ end
274
+
275
+ env_from do
276
+ secret_ref do
277
+ name kube_spec.app_secrets.metadata.name
278
+ end
279
+ end
280
+
281
+ readiness_probe do
282
+ success_threshold 1
283
+ failure_threshold 2
284
+ initial_delay_seconds 15
285
+ period_seconds 3
286
+ timeout_seconds 1
287
+
288
+ http_get do
289
+ path '/healthz'
290
+ port kube_spec.docker.webserver_phase.port
291
+ scheme 'HTTP'
292
+ end
293
+ end
294
+ end
295
+
296
+ init_container(:create_db) do
297
+ name "#{kube_spec.selector_app}-create-db"
298
+ command %w(bundle exec rake kuby:rails_app:db:create_unless_exists)
299
+ end
300
+
301
+ init_container(:migrate_db) do
302
+ name "#{kube_spec.selector_app}-migrate-db"
303
+ command %w(bundle exec rake db:migrate)
304
+ end
305
+
306
+ image_pull_secret do
307
+ name kube_spec.environment.kubernetes.registry_secret.metadata.name
308
+ end
309
+
310
+ restart_policy 'Always'
311
+ service_account_name kube_spec.service_account.metadata.name
312
+ end
313
+ end
314
+ end
315
+ end
316
+
317
+ @deployment.instance_eval(&block) if block
318
+ @deployment
319
+ end
320
+
321
+ def ingress(&block)
322
+ spec = self
323
+ tls_enabled = @tls_enabled
324
+
325
+ @ingress ||= KubeDSL::DSL::Extensions::V1beta1::Ingress.new do
326
+ metadata do
327
+ name "#{spec.selector_app}-ingress"
328
+ namespace spec.namespace.metadata.name
329
+
330
+ annotations do
331
+ add :'kubernetes.io/ingress.class', 'nginx'
332
+ end
333
+ end
334
+
335
+ spec do
336
+ rule do
337
+ host spec.hostname
338
+
339
+ http do
340
+ path do
341
+ path '/'
342
+
343
+ backend do
344
+ service_name spec.service.metadata.name
345
+ service_port spec.service.spec.ports.first.port
346
+ end
347
+ end
348
+ end
349
+ end
350
+
351
+ if tls_enabled
352
+ tls do
353
+ secret_name "#{spec.selector_app}-tls"
354
+ hosts [spec.hostname]
355
+ end
356
+ end
357
+ end
358
+ end
359
+
360
+ @ingress.instance_eval(&block) if block
361
+ @ingress
362
+ end
363
+
364
+ def resources
365
+ @resources ||= [
366
+ service,
367
+ service_account,
368
+ config_map,
369
+ app_secrets,
370
+ deployment,
371
+ ingress,
372
+ *database&.plugin&.resources
373
+ ]
374
+ end
375
+
376
+ def selector_app
377
+ environment.kubernetes.selector_app
378
+ end
379
+
380
+ def role
381
+ WEB_ROLE
382
+ end
383
+
384
+ def docker
385
+ environment.docker
386
+ end
387
+
388
+ def kubernetes
389
+ environment.kubernetes
390
+ end
391
+
392
+ def namespace
393
+ environment.kubernetes.namespace
394
+ end
395
+ end
396
+ end
397
+ end
398
+ end