kuby-core 0.3.0 → 0.7.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 (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
@@ -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,10 +1,11 @@
1
1
  module Kuby
2
2
  module Docker
3
3
  module Packages
4
- autoload :ManagedPackage, 'kuby/docker/packages/managed_package'
5
- autoload :Nodejs, 'kuby/docker/packages/nodejs'
6
- autoload :Package, 'kuby/docker/packages/package'
7
- autoload :Yarn, 'kuby/docker/packages/yarn'
4
+ autoload :ManagedPackage, 'kuby/docker/packages/managed_package'
5
+ autoload :Nodejs, 'kuby/docker/packages/nodejs'
6
+ autoload :Package, 'kuby/docker/packages/package'
7
+ autoload :SimpleManagedPackage, 'kuby/docker/packages/simple_managed_package'
8
+ autoload :Yarn, 'kuby/docker/packages/yarn'
8
9
  end
9
10
  end
10
11
  end
@@ -0,0 +1,25 @@
1
+ module Kuby
2
+ module Docker
3
+ module Packages
4
+ class SimpleManagedPackage
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def package_name_for(distro)
12
+ name
13
+ end
14
+
15
+ def with_version(*)
16
+ self
17
+ end
18
+
19
+ def managed?
20
+ true
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -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,7 +1,7 @@
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
4
+ RE = /\A20[\d]{2}(?:0[1-9]|10|11|12)(?:0[1-9]|1[1-9]|2[1-9]|3[01])\z/.freeze
5
5
  FORMAT = '%Y%m%d%H%M%S'.freeze
6
6
 
7
7
  def self.try_parse(str)
@@ -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
@@ -4,6 +4,7 @@ module Kuby
4
4
  module Kubernetes
5
5
  autoload :MinikubeProvider, 'kuby/kubernetes/minikube_provider'
6
6
  autoload :Deployer, 'kuby/kubernetes/deployer'
7
+ autoload :DeployTask, 'kuby/kubernetes/deploy_task'
7
8
  autoload :DockerConfig, 'kuby/kubernetes/docker_config'
8
9
  autoload :Manifest, 'kuby/kubernetes/manifest'
9
10
  autoload :Monitors, 'kuby/kubernetes/monitors'
@@ -0,0 +1,33 @@
1
+ require 'krane'
2
+ require 'ext/krane/kubernetes_resource'
3
+ require 'kubectl-rb'
4
+
5
+ module Kuby
6
+ module Kubernetes
7
+ class DeployTask
8
+ attr_reader :deploy_task
9
+
10
+ def initialize(**kwargs)
11
+ @deploy_task ||= ::Krane::DeployTask.new(**kwargs)
12
+ end
13
+
14
+ def run!(**kwargs)
15
+ new_path = "#{File.dirname(KubectlRb.executable)}:#{ENV['PATH']}"
16
+
17
+ with_env('PATH' => new_path) do
18
+ deploy_task.run!(**kwargs)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def with_env(new_env)
25
+ old_env = ENV.to_h
26
+ ENV.replace(old_env.merge(new_env))
27
+ yield
28
+ ensure
29
+ ENV.replace(old_env)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,6 +1,4 @@
1
1
  require 'fileutils'
2
- require 'krane'
3
- require 'ext/krane/kubernetes_resource'
4
2
  require 'securerandom'
5
3
  require 'yaml'
6
4
 
@@ -63,7 +61,7 @@ module Kuby
63
61
  File.write(resource_path, resource.to_resource.to_yaml)
64
62
  end
65
63
 
66
- task = ::Krane::DeployTask.new(
64
+ task = ::Kuby::Kubernetes::DeployTask.new(
67
65
  namespace: namespace.metadata.name,
68
66
  context: cli.current_context,
69
67
  filenames: [tmpdir]
@@ -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