kuby-core 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/kuby-core.gemspec +7 -3
  4. data/lib/kuby.rb +9 -7
  5. data/lib/kuby/definition.rb +0 -8
  6. data/lib/kuby/docker/cli.rb +32 -0
  7. data/lib/kuby/docker/errors.rb +1 -0
  8. data/lib/kuby/docker/layer.rb +4 -4
  9. data/lib/kuby/docker/metadata.rb +11 -7
  10. data/lib/kuby/docker/package_phase.rb +2 -2
  11. data/lib/kuby/docker/spec.rb +11 -11
  12. data/lib/kuby/docker/timestamp_tag.rb +6 -3
  13. data/lib/kuby/environment.rb +6 -2
  14. data/lib/kuby/kubernetes.rb +0 -2
  15. data/lib/kuby/kubernetes/deploy_task.rb +0 -1
  16. data/lib/kuby/kubernetes/deployer.rb +7 -7
  17. data/lib/kuby/kubernetes/minikube_provider.rb +4 -0
  18. data/lib/kuby/kubernetes/provider.rb +5 -5
  19. data/lib/kuby/kubernetes/spec.rb +8 -8
  20. data/lib/kuby/plugin.rb +59 -0
  21. data/lib/kuby/plugins.rb +6 -0
  22. data/lib/kuby/plugins/nginx_ingress.rb +71 -0
  23. data/lib/kuby/plugins/rails_app.rb +18 -0
  24. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +117 -0
  25. data/lib/kuby/plugins/rails_app/assets.rb +347 -0
  26. data/lib/kuby/plugins/rails_app/database.rb +75 -0
  27. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/generators/kuby.rb +0 -0
  28. data/lib/kuby/plugins/rails_app/mysql.rb +155 -0
  29. data/lib/kuby/plugins/rails_app/plugin.rb +398 -0
  30. data/lib/kuby/plugins/rails_app/postgres.rb +143 -0
  31. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +11 -0
  32. data/lib/kuby/plugins/rails_app/sqlite.rb +32 -0
  33. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/tasks.rake +10 -2
  34. data/lib/kuby/tasks.rb +28 -9
  35. data/lib/kuby/tasks/kuby.rake +1 -1
  36. data/lib/kuby/version.rb +1 -1
  37. data/spec/docker/timestamp_tag_spec.rb +11 -0
  38. data/spec/spec_helper.rb +102 -0
  39. metadata +50 -27
  40. data/lib/ext/krane/kubernetes_resource.rb +0 -16
  41. data/lib/kuby/kubernetes/plugin.rb +0 -55
  42. data/lib/kuby/kubernetes/plugins.rb +0 -8
  43. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +0 -73
  44. data/lib/kuby/kubernetes/plugins/rails_app.rb +0 -16
  45. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +0 -79
  46. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +0 -154
  47. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +0 -379
  48. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +0 -142
  49. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +0 -13
  50. 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