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
@@ -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