kuby-core 0.4.0 → 0.7.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/kuby-core.gemspec +2 -1
  4. data/lib/kuby.rb +20 -14
  5. data/lib/kuby/definition.rb +20 -14
  6. data/lib/kuby/docker.rb +2 -1
  7. data/lib/kuby/docker/alpine.rb +0 -1
  8. data/lib/kuby/docker/assets_phase.rb +1 -1
  9. data/lib/kuby/docker/bundler_phase.rb +4 -2
  10. data/lib/kuby/docker/cli.rb +32 -0
  11. data/lib/kuby/docker/copy_phase.rb +1 -1
  12. data/lib/kuby/docker/errors.rb +1 -0
  13. data/lib/kuby/docker/inline_layer.rb +15 -0
  14. data/lib/kuby/docker/{phase.rb → layer.rb} +6 -5
  15. data/lib/kuby/docker/layer_stack.rb +30 -4
  16. data/lib/kuby/docker/metadata.rb +9 -1
  17. data/lib/kuby/docker/package_phase.rb +1 -1
  18. data/lib/kuby/docker/setup_phase.rb +1 -1
  19. data/lib/kuby/docker/spec.rb +4 -4
  20. data/lib/kuby/docker/timestamp_tag.rb +6 -3
  21. data/lib/kuby/docker/webserver_phase.rb +1 -1
  22. data/lib/kuby/docker/yarn_phase.rb +1 -1
  23. data/lib/kuby/environment.rb +22 -0
  24. data/lib/kuby/kubernetes/minikube_provider.rb +5 -5
  25. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +12 -0
  26. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +30 -9
  27. data/lib/kuby/kubernetes/plugins/rails_app/generators/kuby.rb +44 -4
  28. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +14 -4
  29. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +15 -14
  30. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +132 -0
  31. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +20 -0
  32. data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +9 -4
  33. data/lib/kuby/kubernetes/spec.rb +1 -1
  34. data/lib/kuby/railtie.rb +0 -4
  35. data/lib/kuby/tasks.rb +35 -0
  36. data/lib/kuby/tasks/kuby.rake +24 -17
  37. data/lib/kuby/version.rb +1 -1
  38. metadata +7 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8199a0db99884940e43a0711cb6ec706d0828a6e553bb327f04d3370ee4c724
4
- data.tar.gz: 4a07cf9105716b0d230a3935b33205321818e414d458e99058dcd23b9abbbf99
3
+ metadata.gz: 12ac1ceff123c3a6e81a967ab7cba85c0900799720c7a448dbb947d6fdd88094
4
+ data.tar.gz: 665f61381cf4857eeac7f079e6439865660ed4daccc7fe7879d4682e362dc4c4
5
5
  SHA512:
6
- metadata.gz: 5e4a38749d22b3dc559296aeaa15cff01d4b51a2bcfd754ec1f780a8a62a849dc85723bb8ee7e2a92f24eaa16c19b5e27e2c238682da2805077507441b87bae6
7
- data.tar.gz: 6070a9a67c1aab75c1e7d1f0b7684bb6fbd899312df57aed642c5c7012d98e886a2880dfca21fec00d38646ba8cbef426fcf56e9a96ba4d4c0dc604b2aa61aa9
6
+ metadata.gz: 2e944f3569d137a187aefd64f72449733b5ed550d5455a17cf860c3b140906c94de7f1e7dce831f534f76ecd7175f2df14d7f62aeef6735d402ab248afbfa770
7
+ data.tar.gz: ae7a508aabf9efcd9d44a53abb42f3b05f9e3f6d4e7fb3635236b9cfe83eda71b71eebfef2f606fac82422bb0844be9873c279a9d69e2c2d377b69999e771a48
@@ -1,3 +1,53 @@
1
+ ## 0.7.1
2
+ * Fix timestamp tag parsing regression caused by adding anchor tags to the regex.
3
+ - Instead, let's rely on `strptime` and return `nil` if it throws an `ArgumentError`.
4
+
5
+ ## 0.7.0
6
+ * Automatically perform `docker login` if not already logged into the Docker registry.
7
+ * Fix timestamp tag parsing issue causing deploy to fail with no available tags.
8
+ - Issue turned out to be ignoring the month of October in the validation regex.
9
+
10
+ ## 0.6.1
11
+ * Fix issue causing database.yml to not be rewritten to point at correct database host.
12
+
13
+ ## 0.6.0
14
+ * Don't load the Rails environment when running Kuby's rake tasks.
15
+ - Kuby's gems are still part of the bundle, but config has been moved out of the initializer and into kuby.rb in the Rails root directory.
16
+ - Internal classes no longer retain a reference to `Rails.application`.
17
+ - Kuby config now requires `environment` blocks:
18
+ ```ruby
19
+ Kuby.define('my-app') do
20
+ environment(:production) do
21
+ ...
22
+ end
23
+
24
+ environment(:staging) do
25
+ ...
26
+ end
27
+ end
28
+ ```
29
+ * Fix `MissingDistroError` caused by not setting a default distro.
30
+ * Create a .dockerignore file when running the Rails generator.
31
+ * Add ability to insert inline Docker layers without having to create a separate class, eg:
32
+ ```ruby
33
+ insert :hello, before: :bundler_phase do |dockerfile|
34
+ dockerfile.run('echo "hello, world"')
35
+ end
36
+ ```
37
+ * Add Postgres database support.
38
+ * Don't install sqlite libs by default.
39
+ * Modify Rails generator
40
+ - Require kuby and load config safely.
41
+ - Provide manual access to credentials via `ActiveSupport::EncryptedConfiguration`, which is necessary now that our rake tasks don't load the Rails environment.
42
+ * Add a convenience method for requesting the amount of block storage for the database.
43
+ * Add the ability to entirely disable database management via `manage_database false`.
44
+ * Avoid deploying nginx-ingress if it's already deployed.
45
+ * Add rake task for running arbitrary `kubectl` commands.
46
+
47
+ ## 0.5.0
48
+ * Fix Rails generators issue causing crash at startup.
49
+ * Add rake task to run arbitrary kubectl commands.
50
+
1
51
  ## 0.4.0
2
52
  * Introduce simple managed package.
3
53
  * Refactor deploy task into its own class.
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency 'colorize', '~> 0.8'
16
16
  s.add_dependency 'docker-remote', '~> 0.1'
17
17
  s.add_dependency 'krane', '~> 1.0'
18
- s.add_dependency 'kuby-cert-manager', '~> 0.1'
18
+ s.add_dependency 'kuby-cert-manager', '~> 0.2'
19
19
  s.add_dependency 'kube-dsl', '~> 0.3'
20
20
  s.add_dependency 'kuby-kube-db', '~> 0.4'
21
21
  s.add_dependency 'kubernetes-cli', '~> 0.2'
@@ -23,5 +23,6 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency 'rouge', '~> 3.0'
24
24
 
25
25
  s.require_path = 'lib'
26
+
26
27
  s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'kuby-core.gemspec']
27
28
  end
@@ -10,6 +10,7 @@ module Kuby
10
10
  autoload :CLIBase, 'kuby/cli_base'
11
11
  autoload :Definition, 'kuby/definition'
12
12
  autoload :Docker, 'kuby/docker'
13
+ autoload :Environment, 'kuby/environment'
13
14
  autoload :Kubernetes, 'kuby/kubernetes'
14
15
  autoload :Middleware, 'kuby/middleware'
15
16
  autoload :Tasks, 'kuby/tasks'
@@ -19,19 +20,25 @@ module Kuby
19
20
 
20
21
  class << self
21
22
  attr_reader :definition
22
- attr_accessor :logger
23
+ attr_writer :logger
23
24
 
24
- def define(environment, app = Rails.application, &block)
25
- environment = environment.to_s
26
- definitions[environment] ||= Definition.new(environment, app, &block)
25
+ def load!
26
+ require ENV['KUBY_CONFIG'] || File.join('.', 'kuby.rb')
27
27
  end
28
28
 
29
- def definitions
30
- @definitions ||= {}
29
+ def define(name, &block)
30
+ raise 'Kuby is already configured' if @definition
31
+
32
+ @definition = Definition.new(name.to_s)
33
+ @definition.instance_eval(&block)
34
+
35
+ @definition.environments.each do |_, env|
36
+ env.kubernetes.after_configuration
37
+ end
31
38
  end
32
39
 
33
- def definition(environment = env)
34
- definitions.fetch(environment.to_s) do
40
+ def environment(name = env)
41
+ definition.environment(name.to_s) do
35
42
  raise UndefinedEnvironmentError, "couldn't find a Kuby environment named "\
36
43
  "'#{environment}'"
37
44
  end
@@ -61,6 +68,10 @@ module Kuby
61
68
  @plugins ||= {}
62
69
  end
63
70
 
71
+ def logger
72
+ @logger ||= BasicLogger.new(STDERR)
73
+ end
74
+
64
75
  def register_package(package_name, package_def = nil)
65
76
  packages[package_name] = case package_def
66
77
  when NilClass
@@ -86,7 +97,7 @@ module Kuby
86
97
 
87
98
  def env
88
99
  ENV.fetch('KUBY_ENV') do
89
- (definitions.keys.first || Rails.env).to_s
100
+ (definition.environments.keys.first || Rails.env).to_s
90
101
  end
91
102
  end
92
103
  end
@@ -114,8 +125,3 @@ Kuby.register_package(:c_toolchain,
114
125
  debian: 'build-essential',
115
126
  alpine: 'build-base'
116
127
  )
117
-
118
- Kuby.register_package(:sqlite_dev,
119
- debian: 'libsqlite3-dev',
120
- alpine: 'sqlite-dev'
121
- )
@@ -1,29 +1,35 @@
1
1
  module Kuby
2
2
  class Definition
3
- attr_reader :environment, :app
3
+ attr_reader :app_name
4
4
 
5
- def initialize(environment, app, &block)
6
- @environment = environment
7
- @app = app
5
+ def initialize(app_name, &block)
6
+ @app_name = app_name
7
+ end
8
+
9
+ def environment(name = Kuby.env, &block)
10
+ name = name.to_s
11
+
12
+ if name
13
+ environments[name] ||= Environment.new(name, self)
14
+ end
15
+
16
+ if block_given?
17
+ environments[name].instance_eval(&block)
18
+ end
8
19
 
9
- instance_eval(&block) if block
10
- kubernetes.after_configuration
20
+ environments[name]
11
21
  end
12
22
 
13
23
  def docker(&block)
14
- @docker ||= Docker::Spec.new(self)
15
- @docker.instance_eval(&block) if block
16
- @docker
24
+ environment.docker(&block)
17
25
  end
18
26
 
19
27
  def kubernetes(&block)
20
- @kubernetes ||= Kubernetes::Spec.new(self)
21
- @kubernetes.instance_eval(&block) if block
22
- @kubernetes
28
+ environment.kubernetes(&block)
23
29
  end
24
30
 
25
- def app_name
26
- @app_name ||= app.class.module_parent.name
31
+ def environments
32
+ @environments ||= {}
27
33
  end
28
34
  end
29
35
  end
@@ -10,12 +10,13 @@ module Kuby
10
10
  autoload :Credentials, 'kuby/docker/credentials'
11
11
  autoload :Debian, 'kuby/docker/debian'
12
12
  autoload :Dockerfile, 'kuby/docker/dockerfile'
13
+ autoload :InlineLayer, 'kuby/docker/inline_layer'
14
+ autoload :Layer, 'kuby/docker/layer'
13
15
  autoload :LayerStack, 'kuby/docker/layer_stack'
14
16
  autoload :LocalTags, 'kuby/docker/local_tags'
15
17
  autoload :Metadata, 'kuby/docker/metadata'
16
18
  autoload :Packages, 'kuby/docker/packages'
17
19
  autoload :PackagePhase, 'kuby/docker/package_phase'
18
- autoload :Phase, 'kuby/docker/phase'
19
20
  autoload :RemoteTags, 'kuby/docker/remote_tags'
20
21
  autoload :SetupPhase, 'kuby/docker/setup_phase'
21
22
  autoload :Spec, 'kuby/docker/spec'
@@ -8,7 +8,6 @@ module Kuby
8
8
  [:nodejs, '12.14.1'],
9
9
  [:yarn, '1.21.1'],
10
10
  [:c_toolchain],
11
- [:sqlite_dev],
12
11
  [:tzdata]
13
12
  ].freeze
14
13
 
@@ -1,6 +1,6 @@
1
1
  module Kuby
2
2
  module Docker
3
- class AssetsPhase < Phase
3
+ class AssetsPhase < Layer
4
4
  def apply_to(dockerfile)
5
5
  dockerfile.run(
6
6
  'bundle', 'exec', 'rake', 'assets:precompile'
@@ -1,6 +1,8 @@
1
+ require 'pathname'
2
+
1
3
  module Kuby
2
4
  module Docker
3
- class BundlerPhase < Phase
5
+ class BundlerPhase < Layer
4
6
  DEFAULT_WITHOUT = ['development', 'test', 'deploy'].freeze
5
7
 
6
8
  attr_accessor :version, :gemfile, :without
@@ -48,7 +50,7 @@ module Kuby
48
50
  .definition
49
51
  .gemfiles
50
52
  .first
51
- .relative_path_from(app.root)
53
+ .relative_path_from(Pathname(Dir.getwd))
52
54
  .to_s
53
55
  end
54
56
  end
@@ -10,6 +10,38 @@ module Kuby
10
10
  @executable = executable || `which docker`.strip
11
11
  end
12
12
 
13
+ def config_file
14
+ if File.exist?(default_config_file)
15
+ default_config_file
16
+ end
17
+ end
18
+
19
+ def default_config_file
20
+ File.join(Dir.home, '.docker', 'config.json')
21
+ end
22
+
23
+ def login(url:, username:, password:)
24
+ cmd = [
25
+ executable, 'login', url, '--username', username, '--password-stdin'
26
+ ]
27
+
28
+ open3_w({}, cmd) do |stdin, _wait_threads|
29
+ stdin.puts(password)
30
+ end
31
+
32
+ unless last_status.success?
33
+ raise LoginError, 'build failed: docker command exited with '\
34
+ "status code #{last_status.exitstatus}"
35
+ end
36
+ end
37
+
38
+ def auths
39
+ return [] unless config_file
40
+
41
+ config = JSON.parse(File.read(config_file))
42
+ config.fetch('auths', {}).keys
43
+ end
44
+
13
45
  def build(dockerfile:, image_url:, tags:)
14
46
  cmd = [
15
47
  executable, 'build',
@@ -1,6 +1,6 @@
1
1
  module Kuby
2
2
  module Docker
3
- class CopyPhase < Phase
3
+ class CopyPhase < Layer
4
4
  DEFAULT_PATHS = ['./'].freeze
5
5
 
6
6
  attr_reader :paths
@@ -2,6 +2,7 @@ module Kuby
2
2
  module Docker
3
3
  class BuildError < StandardError; end
4
4
  class PushError < StandardError; end
5
+ class LoginError < StandardError; end
5
6
 
6
7
  class MissingTagError < StandardError
7
8
  attr_reader :tag
@@ -0,0 +1,15 @@
1
+ module Kuby
2
+ module Docker
3
+ class InlineLayer < Layer
4
+ attr_reader :block
5
+
6
+ def initialize(block)
7
+ @block = block
8
+ end
9
+
10
+ def apply_to(dockerfile)
11
+ block.call(dockerfile)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,18 +1,19 @@
1
1
  module Kuby
2
2
  module Docker
3
- class Phase
3
+ class Layer
4
4
  attr_reader :definition
5
5
 
6
6
  def initialize(definition)
7
7
  @definition = definition
8
8
  end
9
9
 
10
- private
11
-
12
- def app
13
- definition.app
10
+ def apply_to(dockerfile)
11
+ raise NotImplementedError,
12
+ "#{__method__} must be defined in derived classes"
14
13
  end
15
14
 
15
+ private
16
+
16
17
  def metadata
17
18
  definition.docker.metadata
18
19
  end
@@ -15,12 +15,31 @@ module Kuby
15
15
  @stack.each { |name| yield layers[name] }
16
16
  end
17
17
 
18
- def use(name, layer)
18
+ def use(name, layer = nil, &block)
19
19
  stack << name
20
- layers[name] = layer
20
+
21
+ if layer
22
+ layers[name] = layer
23
+ elsif block_given?
24
+ layers[name] = InlineLayer.new(block)
25
+ else
26
+ raise "Must either pass a layer object or a block to `#{__method__}'"
27
+ end
21
28
  end
22
29
 
23
- def insert(name, layer, options = {})
30
+ def insert(name, layer = nil, options = {}, &block)
31
+ # this is truly gross but it's the only way I can think of to be able
32
+ # to call insert these two ways:
33
+ #
34
+ # insert :foo, FooLayer.new, before: :bundler_phase
35
+ # insert :foo, before: :bundler_phase do
36
+ # ...
37
+ # end
38
+ if layer.is_a?(Hash)
39
+ insert(name, nil, options.merge(layer), &block)
40
+ return
41
+ end
42
+
24
43
  existing_name = options[:before] || options[:after]
25
44
  idx = stack.index(existing_name)
26
45
 
@@ -30,7 +49,14 @@ module Kuby
30
49
 
31
50
  idx += 1 if options[:after]
32
51
  stack.insert(idx, name)
33
- layers[name] = layer
52
+
53
+ if layer
54
+ layers[name] = layer
55
+ elsif block_given?
56
+ layers[name] = InlineLayer.new(block)
57
+ else
58
+ raise "Must either pass a layer object or a block to `#{__method__}'"
59
+ end
34
60
  end
35
61
 
36
62
  def delete(name)
@@ -8,7 +8,7 @@ module Kuby
8
8
  LATEST_TAG = 'latest'
9
9
 
10
10
  attr_accessor :image_url
11
- attr_reader :definition, :distro
11
+ attr_reader :definition
12
12
 
13
13
  def initialize(definition)
14
14
  @definition = definition
@@ -28,6 +28,10 @@ module Kuby
28
28
  end
29
29
  end
30
30
 
31
+ def image_hostname
32
+ @image_hostname ||= URI(image_host).host
33
+ end
34
+
31
35
  def image_repo
32
36
  @image_repo ||= if image_url.include?('/')
33
37
  parse_url(image_url).path.sub(/\A\//, '')
@@ -62,6 +66,10 @@ module Kuby
62
66
  t.to_s
63
67
  end
64
68
 
69
+ def distro
70
+ @distro || DEFAULT_DISTRO
71
+ end
72
+
65
73
  def distro=(distro_name)
66
74
  @distro = distro_name
67
75
  end
@@ -1,6 +1,6 @@
1
1
  module Kuby
2
2
  module Docker
3
- class PackagePhase < Phase
3
+ class PackagePhase < Layer
4
4
  attr_reader :operations
5
5
 
6
6
  def initialize(*args)
@@ -1,6 +1,6 @@
1
1
  module Kuby
2
2
  module Docker
3
- class SetupPhase < Phase
3
+ class SetupPhase < Layer
4
4
  DEFAULT_WORKING_DIR = '/usr/src/app'.freeze
5
5
 
6
6
  attr_accessor :base_image, :working_dir
@@ -50,12 +50,12 @@ module Kuby
50
50
  metadata.image_url = url
51
51
  end
52
52
 
53
- def use(*args)
54
- layer_stack.use(*args)
53
+ def use(*args, &block)
54
+ layer_stack.use(*args, &block)
55
55
  end
56
56
 
57
- def insert(*args)
58
- layer_stack.insert(*args)
57
+ def insert(*args, &block)
58
+ layer_stack.insert(*args, &block)
59
59
  end
60
60
 
61
61
  def delete(*args)
@@ -1,13 +1,16 @@
1
1
  module Kuby
2
2
  module Docker
3
3
  class TimestampTag
4
- RE = /20[\d]{2}(?:0[1-9]|11|12)(?:0[1-9]|1[1-9]|2[1-9]|3[01])/.freeze
5
4
  FORMAT = '%Y%m%d%H%M%S'.freeze
6
5
 
7
6
  def self.try_parse(str)
8
- if str =~ RE
9
- new(Time.strptime(str, FORMAT))
7
+ time = begin
8
+ Time.strptime(str, FORMAT)
9
+ rescue ArgumentError
10
+ return nil
10
11
  end
12
+
13
+ new(time)
11
14
  end
12
15
 
13
16
  attr_reader :time
@@ -1,6 +1,6 @@
1
1
  module Kuby
2
2
  module Docker
3
- class WebserverPhase < Phase
3
+ class WebserverPhase < Layer
4
4
  class Puma
5
5
  attr_reader :phase
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Kuby
2
2
  module Docker
3
- class YarnPhase < Phase
3
+ class YarnPhase < Layer
4
4
  def apply_to(dockerfile)
5
5
  dockerfile.copy('package.json', '.')
6
6
  dockerfile.copy('yarn.lock*', '.')
@@ -0,0 +1,22 @@
1
+ module Kuby
2
+ class Environment
3
+ attr_reader :name, :definition
4
+
5
+ def initialize(name, definition, &block)
6
+ @name = name
7
+ @definition = definition
8
+ end
9
+
10
+ def docker(&block)
11
+ @docker ||= Docker::Spec.new(definition)
12
+ @docker.instance_eval(&block) if block
13
+ @docker
14
+ end
15
+
16
+ def kubernetes(&block)
17
+ @kubernetes ||= Kubernetes::Spec.new(definition)
18
+ @kubernetes.instance_eval(&block) if block
19
+ @kubernetes
20
+ end
21
+ end
22
+ end
@@ -26,11 +26,6 @@ module Kuby
26
26
  rails_app.resources.delete(rails_app.ingress)
27
27
  rails_app.service.spec { type 'LoadBalancer' }
28
28
  end
29
-
30
- configure do
31
- # default kubeconfig path
32
- kubeconfig File.join(ENV['HOME'], '.kube', 'config')
33
- end
34
29
  end
35
30
 
36
31
  def kubeconfig_path
@@ -45,6 +40,11 @@ module Kuby
45
40
 
46
41
  def after_initialize
47
42
  @config = Config.new
43
+
44
+ configure do
45
+ # default kubeconfig path
46
+ kubeconfig File.join(ENV['HOME'], '.kube', 'config')
47
+ end
48
48
  end
49
49
  end
50
50
  end
@@ -27,6 +27,11 @@ module Kuby
27
27
  def setup
28
28
  Kuby.logger.info('Deploying nginx ingress resources')
29
29
 
30
+ if already_deployed?
31
+ Kuby.logger.info('Nginx ingress already deployed, skipping')
32
+ return
33
+ end
34
+
30
35
  SETUP_RESOURCES.each do |uri|
31
36
  uri = uri % { provider: @config.provider || DEFAULT_PROVIDER }
32
37
  kubernetes_cli.apply_uri(uri)
@@ -48,6 +53,13 @@ module Kuby
48
53
 
49
54
  private
50
55
 
56
+ def already_deployed?
57
+ kubernetes_cli.get_object('Service', 'ingress-nginx', 'ingress-nginx')
58
+ true
59
+ rescue KubernetesCLI::GetResourceError
60
+ return false
61
+ end
62
+
51
63
  def after_initialize
52
64
  @config = Config.new
53
65
  end
@@ -1,3 +1,6 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
1
4
  module Kuby
2
5
  module Kubernetes
3
6
  module Plugins
@@ -11,27 +14,29 @@ module Kuby
11
14
  'postgresql' => Postgres
12
15
  }.freeze
13
16
 
14
- def self.get(definition)
15
- new(definition).database
17
+ def self.get(rails_app)
18
+ if rails_app.manage_database?
19
+ new(rails_app).database
20
+ end
16
21
  end
17
22
 
18
23
  def self.get_adapter(adapter)
19
24
  ADAPTER_MAP.fetch(adapter) do
20
- raise UnsupportedDatabaseError, "Kuby does not support the '#{adapter}'"\
25
+ raise UnsupportedDatabaseError, "Kuby does not support the '#{adapter}' "\
21
26
  'database adapter'
22
27
  end
23
28
  end
24
29
 
25
- attr_reader :definition
30
+ attr_reader :rails_app
26
31
 
27
- def initialize(definition)
28
- @definition = definition
32
+ def initialize(rails_app)
33
+ @rails_app = rails_app
29
34
  end
30
35
 
31
36
  def database
32
37
  @database ||= self.class
33
38
  .get_adapter(adapter)
34
- .new(definition, environment, db_configs)
39
+ .new(rails_app, environment, db_configs)
35
40
  end
36
41
 
37
42
  private
@@ -45,11 +50,27 @@ module Kuby
45
50
  end
46
51
 
47
52
  def environment
48
- @environment ||= definition.environment
53
+ @environment ||= rails_app.definition.environment.name
49
54
  end
50
55
 
51
56
  def db_configs
52
- @db_configs ||= definition.app.config.database_configuration
57
+ @db_configs ||= YAML.load(ERB.new(File.read(db_config_path)).result)
58
+ end
59
+
60
+ def db_config_path
61
+ @db_config_path ||= begin
62
+ db_config_paths.first or
63
+ raise "Couldn't find database config at #{rails_app.root}"
64
+ end
65
+ end
66
+
67
+ def db_config_paths
68
+ @db_config_paths ||=
69
+ Dir.glob(
70
+ File.join(
71
+ rails_app.root, 'config', 'database.{yml,erb,yml.erb,yaml,yaml.erb}'
72
+ )
73
+ )
53
74
  end
54
75
  end
55
76
  end
@@ -1,3 +1,4 @@
1
+ require 'rails/generators'
1
2
  require 'rails/generators/base'
2
3
 
3
4
  class KubyGenerator < Rails::Generators::Base
@@ -5,15 +6,35 @@ class KubyGenerator < Rails::Generators::Base
5
6
  initializer(
6
7
  'kuby.rb',
7
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
+
8
22
  # Define a production Kuby deploy environment
9
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
+
10
31
  docker do
11
32
  # Configure your Docker registry credentials here. Add them to your
12
33
  # Rails credentials file by running `bundle exec rake credentials:edit`.
13
34
  credentials do
14
- username Rails.application.credentials[:KUBY_DOCKER_USERNAME]
15
- password Rails.application.credentials[:KUBY_DOCKER_PASSWORD]
16
- email Rails.application.credentials[:KUBY_DOCKER_EMAIL]
35
+ username app_creds[:KUBY_DOCKER_USERNAME]
36
+ password app_creds[:KUBY_DOCKER_PASSWORD]
37
+ email app_creds[:KUBY_DOCKER_EMAIL]
17
38
  end
18
39
 
19
40
  # Configure the URL to your Docker image here, eg:
@@ -27,7 +48,8 @@ class KubyGenerator < Rails::Generators::Base
27
48
  # Add a plugin that facilitates deploying a Rails app.
28
49
  add_plugin :rails_app
29
50
 
30
- # Use minikube as the default provider.
51
+ # Use minikube as the provider, which is the default installed by
52
+ # Docker Desktop.
31
53
  # See: https://github.com/kubernetes/minikube
32
54
  #
33
55
  # Note: you will likely want to use a different provider when deploying
@@ -40,4 +62,22 @@ class KubyGenerator < Rails::Generators::Base
40
62
  END
41
63
  )
42
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
43
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
@@ -1,62 +1,69 @@
1
+ require 'shellwords'
2
+
1
3
  namespace :kuby do
2
4
  def tasks
5
+ Kuby.load!
3
6
  @tasks ||= Kuby::Tasks.new(Kuby.definition)
4
7
  end
5
8
 
6
- task dockerfile: :environment do
9
+ task :dockerfile do
7
10
  tasks.print_dockerfile
8
11
  end
9
12
 
10
- task build: :environment do
13
+ task :build do
11
14
  tasks.build
12
15
  end
13
16
 
14
- task run: :environment do
17
+ task :run do
15
18
  tasks.run
16
19
  end
17
20
 
18
- task push: :environment do
21
+ task :push do
19
22
  tasks.push
20
23
  end
21
24
 
22
- task resources: :environment do
25
+ task :resources do
23
26
  tasks.print_resources
24
27
  end
25
28
 
26
- task deploy: :environment do
27
- Kuby.definition.kubernetes.deploy
29
+ task :kubectl, [:cmd] do |_, args|
30
+ tasks.kubectl(Shellwords.shellsplit(args[:cmd]))
31
+ end
32
+
33
+ task :deploy do
34
+ tasks.deploy
28
35
  end
29
36
 
30
- task rollback: :environment do
31
- Kuby.definition.kubernetes.rollback
37
+ task :rollback do
38
+ tasks.rollback
32
39
  end
33
40
 
34
- task kubeconfig: :environment do
41
+ task :kubeconfig do
35
42
  tasks.print_kubeconfig
36
43
  end
37
44
 
38
- task setup: :environment do
39
- Kuby.definition.kubernetes.setup
45
+ task :setup do
46
+ tasks.setup
40
47
  end
41
48
 
42
49
  namespace :remote do
43
- task logs: :environment do
50
+ task :logs do
44
51
  tasks.remote_logs
45
52
  end
46
53
 
47
- task status: :environment do
54
+ task :status do
48
55
  tasks.remote_status
49
56
  end
50
57
 
51
- task shell: :environment do
58
+ task :shell do
52
59
  tasks.remote_shell
53
60
  end
54
61
 
55
- task console: :environment do
62
+ task :console do
56
63
  tasks.remote_console
57
64
  end
58
65
 
59
- task dbconsole: :environment do
66
+ task :dbconsole do
60
67
  tasks.remote_dbconsole
61
68
  end
62
69
  end
@@ -1,3 +1,3 @@
1
1
  module Kuby
2
- VERSION = '0.4.0'
2
+ VERSION = '0.7.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kuby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-27 00:00:00.000000000 Z
11
+ date: 2020-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.1'
61
+ version: '0.2'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0.1'
68
+ version: '0.2'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: kube-dsl
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +164,8 @@ files:
164
164
  - lib/kuby/docker/debian.rb
165
165
  - lib/kuby/docker/dockerfile.rb
166
166
  - lib/kuby/docker/errors.rb
167
+ - lib/kuby/docker/inline_layer.rb
168
+ - lib/kuby/docker/layer.rb
167
169
  - lib/kuby/docker/layer_stack.rb
168
170
  - lib/kuby/docker/local_tags.rb
169
171
  - lib/kuby/docker/metadata.rb
@@ -175,7 +177,6 @@ files:
175
177
  - lib/kuby/docker/packages/package.rb
176
178
  - lib/kuby/docker/packages/simple_managed_package.rb
177
179
  - lib/kuby/docker/packages/yarn.rb
178
- - lib/kuby/docker/phase.rb
179
180
  - lib/kuby/docker/remote_tags.rb
180
181
  - lib/kuby/docker/setup_phase.rb
181
182
  - lib/kuby/docker/spec.rb
@@ -183,6 +184,7 @@ files:
183
184
  - lib/kuby/docker/timestamp_tag.rb
184
185
  - lib/kuby/docker/webserver_phase.rb
185
186
  - lib/kuby/docker/yarn_phase.rb
187
+ - lib/kuby/environment.rb
186
188
  - lib/kuby/kubernetes.rb
187
189
  - lib/kuby/kubernetes/deploy_task.rb
188
190
  - lib/kuby/kubernetes/deployer.rb