kuby-core 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +3 -160
  4. data/kuby-core.gemspec +2 -1
  5. data/lib/kuby.rb +32 -17
  6. data/lib/kuby/definition.rb +20 -14
  7. data/lib/kuby/docker.rb +2 -1
  8. data/lib/kuby/docker/alpine.rb +0 -1
  9. data/lib/kuby/docker/assets_phase.rb +1 -1
  10. data/lib/kuby/docker/bundler_phase.rb +4 -2
  11. data/lib/kuby/docker/cli.rb +32 -0
  12. data/lib/kuby/docker/copy_phase.rb +1 -1
  13. data/lib/kuby/docker/errors.rb +1 -0
  14. data/lib/kuby/docker/inline_layer.rb +15 -0
  15. data/lib/kuby/docker/{phase.rb → layer.rb} +6 -5
  16. data/lib/kuby/docker/layer_stack.rb +30 -4
  17. data/lib/kuby/docker/metadata.rb +9 -1
  18. data/lib/kuby/docker/package_phase.rb +1 -1
  19. data/lib/kuby/docker/packages.rb +5 -4
  20. data/lib/kuby/docker/packages/simple_managed_package.rb +25 -0
  21. data/lib/kuby/docker/setup_phase.rb +1 -1
  22. data/lib/kuby/docker/spec.rb +4 -4
  23. data/lib/kuby/docker/timestamp_tag.rb +1 -1
  24. data/lib/kuby/docker/webserver_phase.rb +1 -1
  25. data/lib/kuby/docker/yarn_phase.rb +1 -1
  26. data/lib/kuby/environment.rb +22 -0
  27. data/lib/kuby/kubernetes.rb +1 -0
  28. data/lib/kuby/kubernetes/deploy_task.rb +33 -0
  29. data/lib/kuby/kubernetes/deployer.rb +1 -3
  30. data/lib/kuby/kubernetes/minikube_provider.rb +5 -5
  31. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +12 -0
  32. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +30 -9
  33. data/lib/kuby/kubernetes/plugins/rails_app/generators/kuby.rb +83 -0
  34. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +14 -4
  35. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +15 -14
  36. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +132 -0
  37. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +20 -0
  38. data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +9 -4
  39. data/lib/kuby/kubernetes/spec.rb +1 -1
  40. data/lib/kuby/railtie.rb +0 -4
  41. data/lib/kuby/tasks.rb +35 -0
  42. data/lib/kuby/tasks/kuby.rake +24 -17
  43. data/lib/kuby/version.rb +1 -1
  44. metadata +10 -5
@@ -0,0 +1,83 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/base'
3
+
4
+ class KubyGenerator < Rails::Generators::Base
5
+ def create_initializer_file
6
+ initializer(
7
+ 'kuby.rb',
8
+ <<~END
9
+ require 'kuby'
10
+
11
+ Kuby.load!
12
+ END
13
+ )
14
+ end
15
+
16
+ def create_config_file
17
+ create_file(
18
+ 'kuby.rb',
19
+ <<~END
20
+ require 'active_support/encrypted_configuration'
21
+
22
+ # Define a production Kuby deploy environment
23
+ Kuby.define(:production) do
24
+ app_creds = ActiveSupport::EncryptedConfiguration.new(
25
+ config_path: File.join('config', 'credentials.yml.enc'),
26
+ key_path: File.join('config', 'master.key'),
27
+ env_key: 'RAILS_MASTER_KEY',
28
+ raise_if_missing_key: true
29
+ )
30
+
31
+ docker do
32
+ # Configure your Docker registry credentials here. Add them to your
33
+ # Rails credentials file by running `bundle exec rake credentials:edit`.
34
+ credentials do
35
+ username app_creds[:KUBY_DOCKER_USERNAME]
36
+ password app_creds[:KUBY_DOCKER_PASSWORD]
37
+ email app_creds[:KUBY_DOCKER_EMAIL]
38
+ end
39
+
40
+ # Configure the URL to your Docker image here, eg:
41
+ # image_url 'foo.bar.com/me/myproject'
42
+ #
43
+ # If you're using Gitlab's Docker registry, try something like this:
44
+ # image_url 'registry.gitlab.com/<username>/<repo>'
45
+ end
46
+
47
+ kubernetes do
48
+ # Add a plugin that facilitates deploying a Rails app.
49
+ add_plugin :rails_app
50
+
51
+ # Use minikube as the provider, which is the default installed by
52
+ # Docker Desktop.
53
+ # See: https://github.com/kubernetes/minikube
54
+ #
55
+ # Note: you will likely want to use a different provider when deploying
56
+ # your application into a production environment. To configure a different
57
+ # provider, add the corresponding gem to your gemfile and update the
58
+ # following line according to the provider gem's README.
59
+ provider :minikube
60
+ end
61
+ end
62
+ END
63
+ )
64
+ end
65
+
66
+ def create_dockerignore
67
+ create_file(
68
+ '.dockerignore',
69
+ <<~END
70
+ .bundle/
71
+ vendor/bundle
72
+ node_modules/
73
+ .node_modules/
74
+ **/.git*
75
+ tmp/
76
+ log/
77
+ engines/**/log/
78
+ engines/**/tmp/
79
+ public/assets
80
+ END
81
+ )
82
+ end
83
+ end
@@ -56,6 +56,20 @@ module Kuby
56
56
  end
57
57
  end
58
58
 
59
+ def storage(amount)
60
+ database do
61
+ spec do
62
+ storage do
63
+ resources do
64
+ requests do
65
+ set :storage, amount
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
59
73
  def secret(&block)
60
74
  context = self
61
75
 
@@ -118,10 +132,6 @@ module Kuby
118
132
  definition.kubernetes
119
133
  end
120
134
 
121
- def app
122
- definition.app
123
- end
124
-
125
135
  private
126
136
 
127
137
  def config
@@ -13,12 +13,17 @@ module Kuby
13
13
  ENV_SECRETS = [MASTER_KEY_VAR].freeze
14
14
  ENV_EXCLUDE = ['RAILS_ENV'].freeze
15
15
 
16
- value_fields :hostname, :tls_enabled, :database, :replicas
16
+ value_field :root, default: '.'
17
+ value_fields :hostname, :tls_enabled
18
+ value_fields :manage_database, :database, :replicas
19
+
20
+ alias_method :manage_database?, :manage_database
17
21
 
18
22
  def initialize(definition)
19
23
  @definition = definition
20
24
  @tls_enabled = true
21
25
  @replicas = 1
26
+ @manage_database = true
22
27
  end
23
28
 
24
29
  def configure(&block)
@@ -26,11 +31,7 @@ module Kuby
26
31
  end
27
32
 
28
33
  def after_configuration
29
- # currently Database.get doesn't return nil, but this if statement
30
- # is here as a placeholder to indicate we'd like to be able to
31
- # handle Rails apps that don't use a database, i.e. don't have
32
- # activerecord configured
33
- if @database = Database.get(definition)
34
+ if @database = Database.get(self)
34
35
  definition.kubernetes.plugins[database] = @database
35
36
  definition.kubernetes.add_plugin(:kube_db)
36
37
 
@@ -43,7 +44,11 @@ module Kuby
43
44
  definition.kubernetes.add_plugin(:nginx_ingress)
44
45
 
45
46
  if @tls_enabled
46
- definition.kubernetes.add_plugin(:cert_manager)
47
+ context = self
48
+
49
+ definition.kubernetes.add_plugin(:cert_manager) do
50
+ email context.definition.docker.credentials.email
51
+ end
47
52
  end
48
53
  end
49
54
 
@@ -178,9 +183,9 @@ module Kuby
178
183
  if master_key = ENV[MASTER_KEY_VAR]
179
184
  add MASTER_KEY_VAR.to_sym, master_key
180
185
  else
181
- master_key_path = spec.app.root.join('config', 'master.key')
186
+ master_key_path = File.join(spec.root, 'config', 'master.key')
182
187
 
183
- if master_key_path.exist?
188
+ if File.exist?(master_key_path)
184
189
  add MASTER_KEY_VAR.to_sym, File.read(master_key_path).strip
185
190
  end
186
191
  end
@@ -344,7 +349,7 @@ module Kuby
344
349
  app_secrets,
345
350
  deployment,
346
351
  ingress,
347
- *database.resources
352
+ *database&.resources
348
353
  ]
349
354
  end
350
355
 
@@ -364,10 +369,6 @@ module Kuby
364
369
  definition.kubernetes
365
370
  end
366
371
 
367
- def app
368
- definition.app
369
- end
370
-
371
372
  def namespace
372
373
  definition.kubernetes.namespace
373
374
  end
@@ -1,10 +1,142 @@
1
+ require 'kube-dsl'
2
+ require 'kuby/kube-db'
3
+
1
4
  module Kuby
2
5
  module Kubernetes
3
6
  module Plugins
4
7
  module RailsApp
5
8
  class Postgres < Kuby::Kubernetes::Plugin
9
+ ROLE = 'web'.freeze
10
+
11
+ attr_reader :definition, :environment, :configs
12
+
13
+ def initialize(definition, environment, configs)
14
+ @definition = definition
15
+ @environment = environment
16
+ @configs = configs
17
+
18
+ user(config['username'])
19
+ password(config['password'])
20
+ end
21
+
22
+ def resources
23
+ @resources ||= [secret, database]
24
+ end
25
+
26
+ def after_configuration
27
+ definition.docker.package_phase.add(:postgres_dev)
28
+ definition.docker.package_phase.add(:postgres_client)
29
+ end
30
+
31
+ def host
32
+ # host is the same as the name thanks to k8s DNS
33
+ @host ||= database.metadata.name
34
+ end
35
+
36
+ def rewritten_configs
37
+ # deep dup
38
+ @rewritten_configs ||= Marshal.load(Marshal.dump(configs)).tap do |new_configs|
39
+ new_configs[environment]['host'] = host
40
+ end
41
+ end
42
+
43
+ def user(user)
44
+ secret do
45
+ data do
46
+ set :POSTGRES_USER, user
47
+ end
48
+ end
49
+ end
50
+
51
+ def password(password)
52
+ secret do
53
+ data do
54
+ set :POSTGRES_PASSWORD, password
55
+ end
56
+ end
57
+ end
58
+
59
+ def secret(&block)
60
+ context = self
61
+
62
+ @secret ||= KubeDSL.secret do
63
+ metadata do
64
+ name "#{context.base_name}-postgres-secret"
65
+ namespace context.kubernetes.namespace.metadata.name
66
+ end
67
+
68
+ type 'Opaque'
69
+ end
70
+
71
+ @secret.instance_eval(&block) if block
72
+ @secret
73
+ end
74
+
75
+ def database(&block)
76
+ context = self
77
+
78
+ @database ||= Kuby::KubeDB.postgres do
79
+ api_version 'kubedb.com/v1alpha1'
80
+
81
+ metadata do
82
+ name "#{context.base_name}-postgres"
83
+ namespace context.kubernetes.namespace.metadata.name
84
+ end
85
+
86
+ spec do
87
+ database_secret do
88
+ secret_name context.secret.metadata.name
89
+ end
90
+
91
+ version '11.2'
92
+ standby_mode 'Hot'
93
+ streaming_mode 'asynchronous'
94
+ storage_type 'Durable'
95
+
96
+ storage do
97
+ storage_class_name context.kubernetes.provider.storage_class_name
98
+ access_modes ['ReadWriteOnce']
99
+
100
+ resources do
101
+ requests do
102
+ add :storage, '10Gi'
103
+ end
104
+ end
105
+ end
106
+
107
+ termination_policy 'DoNotTerminate'
108
+ end
109
+ end
110
+
111
+ @database.instance_eval(&block) if block
112
+ @database
113
+ end
114
+
115
+ def base_name
116
+ @base_name ||= "#{kubernetes.selector_app}-#{ROLE}"
117
+ end
118
+
119
+ def kubernetes
120
+ definition.kubernetes
121
+ end
122
+
123
+ private
124
+
125
+ def config
126
+ configs[environment]
127
+ end
6
128
  end
7
129
  end
8
130
  end
9
131
  end
10
132
  end
133
+
134
+ Kuby.register_package(:postgres_dev,
135
+ debian: 'postgresql-client',
136
+ alpine: 'postgresql-dev'
137
+ )
138
+
139
+ Kuby.register_package(:postgres_client,
140
+ debian: 'postgresql-client',
141
+ alpine: 'postgresql-client'
142
+ )
@@ -3,8 +3,28 @@ module Kuby
3
3
  module Plugins
4
4
  module RailsApp
5
5
  class Sqlite < Kuby::Kubernetes::Plugin
6
+ attr_reader :definition
7
+
8
+ def initialize(definition, *)
9
+ @definition = definition
10
+ end
11
+
12
+ def after_configuration
13
+ definition.docker.package_phase.add(:sqlite_dev)
14
+ definition.docker.package_phase.add(:sqlite_client)
15
+ end
6
16
  end
7
17
  end
8
18
  end
9
19
  end
10
20
  end
21
+
22
+ Kuby.register_package(:sqlite_dev,
23
+ debian: 'libsqlite3-dev',
24
+ alpine: 'sqlite-dev'
25
+ )
26
+
27
+ Kuby.register_package(:sqlite_client,
28
+ debian: 'sqlite3',
29
+ alpine: 'sqlite'
30
+ )
@@ -3,11 +3,16 @@ require 'rake'
3
3
  namespace :kuby do
4
4
  namespace :rails_app do
5
5
  namespace :db do
6
- task rewrite_config: :environment do
7
- config_file = Kuby.definition.app.root.join('config', 'database.yml')
6
+ task :rewrite_config do
7
+ Kuby.load!
8
+
9
+ config_file = File.join(Kuby.definition.kubernetes.plugin(:rails_app).root, 'config', 'database.yml')
8
10
  database = Kuby.definition.kubernetes.plugin(:rails_app).database
9
- File.write(config_file, YAML.dump(database.rewritten_configs))
10
- Kuby.logger.info("Wrote #{config_file}")
11
+
12
+ if database.respond_to?(:rewritten_configs)
13
+ File.write(config_file, YAML.dump(database.rewritten_configs))
14
+ Kuby.logger.info("Wrote #{config_file}")
15
+ end
11
16
  end
12
17
 
13
18
  task :create_unless_exists do
@@ -118,7 +118,7 @@ module Kuby
118
118
 
119
119
  @namespace ||= KubeDSL.namespace do
120
120
  metadata do
121
- name "#{spec.selector_app}-#{spec.definition.environment}"
121
+ name "#{spec.selector_app}-#{spec.definition.environment.name}"
122
122
  end
123
123
  end
124
124
 
@@ -7,10 +7,6 @@ module Kuby
7
7
  load File.expand_path(File.join('tasks', 'kuby.rake'), __dir__)
8
8
  end
9
9
 
10
- initializer 'kuby.startup' do |_app|
11
- Kuby.logger = Kuby::BasicLogger.new(STDERR)
12
- end
13
-
14
10
  initializer 'kuby.health_check_middleware' do |app|
15
11
  app.middleware.use Kuby::Middleware::HealthCheck
16
12
  end
@@ -16,6 +16,10 @@ module Kuby
16
16
  puts formatter.format(tokens)
17
17
  end
18
18
 
19
+ def setup
20
+ definition.kubernetes.setup
21
+ end
22
+
19
23
  def build
20
24
  docker.cli.build(
21
25
  dockerfile: docker.to_dockerfile,
@@ -35,6 +39,24 @@ module Kuby
35
39
  end
36
40
 
37
41
  def push
42
+ hostname = docker.metadata.image_hostname
43
+
44
+ unless docker.cli.auths.include?(hostname)
45
+ Kuby.logger.info("Attempting to log in to registry at #{hostname}")
46
+
47
+ begin
48
+ docker.cli.login(
49
+ url: docker.metadata.image_host,
50
+ username: docker.credentials.username,
51
+ password: docker.credentials.password
52
+ )
53
+ rescue Kuby::Docker::LoginError => e
54
+ Kuby.logger.fatal("Couldn't log in to the registry at #{hostname}")
55
+ Kuby.logger.fatal(e.message)
56
+ return
57
+ end
58
+ end
59
+
38
60
  image_url = docker.metadata.image_url
39
61
 
40
62
  begin
@@ -46,9 +68,18 @@ module Kuby
46
68
  'Docker image before running this task.'
47
69
 
48
70
  Kuby.logger.fatal(msg)
71
+ Kuby.logger.fatal(e.message)
49
72
  end
50
73
  end
51
74
 
75
+ def deploy
76
+ definition.kubernetes.deploy
77
+ end
78
+
79
+ def rollback
80
+ definition.kubernetes.rollback
81
+ end
82
+
52
83
  def print_resources
53
84
  kubernetes.before_deploy
54
85
 
@@ -63,6 +94,10 @@ module Kuby
63
94
  puts File.read(path)
64
95
  end
65
96
 
97
+ def kubectl(*cmd)
98
+ kubernetes_cli.run_cmd(cmd)
99
+ end
100
+
66
101
  def remote_logs
67
102
  kubernetes_cli.logtail(namespace, match_labels.serialize)
68
103
  end