kuby-core 0.8.1 → 0.9.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -1
  3. data/README.md +11 -1
  4. data/bin/kuby +4 -0
  5. data/kuby-core.gemspec +5 -2
  6. data/lib/kuby.rb +46 -18
  7. data/lib/kuby/basic_logger.rb +13 -0
  8. data/lib/kuby/cli_base.rb +81 -8
  9. data/lib/kuby/commands.rb +220 -0
  10. data/lib/kuby/definition.rb +1 -3
  11. data/lib/kuby/dev_setup.rb +255 -0
  12. data/lib/kuby/docker.rb +1 -0
  13. data/lib/kuby/docker/bundler_phase.rb +3 -3
  14. data/lib/kuby/docker/cli.rb +13 -1
  15. data/lib/kuby/docker/dev_spec.rb +131 -0
  16. data/lib/kuby/docker/dockerfile.rb +16 -1
  17. data/lib/kuby/docker/layer_stack.rb +4 -0
  18. data/lib/kuby/docker/local_tags.rb +4 -0
  19. data/lib/kuby/docker/metadata.rb +0 -22
  20. data/lib/kuby/docker/setup_phase.rb +3 -2
  21. data/lib/kuby/docker/spec.rb +31 -5
  22. data/lib/kuby/environment.rb +10 -1
  23. data/lib/kuby/kubernetes.rb +9 -9
  24. data/lib/kuby/kubernetes/deploy_task.rb +4 -0
  25. data/lib/kuby/kubernetes/deployer.rb +63 -11
  26. data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +4 -4
  27. data/lib/kuby/kubernetes/provider.rb +8 -4
  28. data/lib/kuby/kubernetes/spec.rb +23 -22
  29. data/lib/kuby/plugin_registry.rb +27 -0
  30. data/lib/kuby/plugins/rails_app/generators/kuby.rb +3 -15
  31. data/lib/kuby/plugins/rails_app/plugin.rb +230 -40
  32. data/lib/kuby/rails_commands.rb +89 -0
  33. data/lib/kuby/railtie.rb +0 -4
  34. data/lib/kuby/tasks.rb +76 -23
  35. data/lib/kuby/version.rb +1 -1
  36. data/spec/docker/metadata_spec.rb +0 -108
  37. data/spec/docker/spec_spec.rb +266 -0
  38. data/spec/spec_helper.rb +8 -1
  39. metadata +44 -9
  40. data/lib/kuby/tasks/kuby.rake +0 -70
@@ -1,3 +1,5 @@
1
+ require 'digest'
2
+
1
3
  module Kuby
2
4
  module Docker
3
5
  class Dockerfile
@@ -65,6 +67,10 @@ module Kuby
65
67
  def to_s; "CMD #{super}"; end
66
68
  end
67
69
 
70
+ class Arg < Command
71
+ def to_s; "ARG #{super}"; end
72
+ end
73
+
68
74
  attr_reader :commands, :cursor
69
75
 
70
76
  def initialize
@@ -84,6 +90,10 @@ module Kuby
84
90
  add Env.new(*args)
85
91
  end
86
92
 
93
+ def arg(*args)
94
+ add Arg.new(*args)
95
+ end
96
+
87
97
  def run(*args)
88
98
  add Run.new(*args)
89
99
  end
@@ -101,7 +111,12 @@ module Kuby
101
111
  end
102
112
 
103
113
  def to_s
104
- commands.map(&:to_s).join("\n")
114
+ # ensure trailing newline
115
+ "#{commands.map(&:to_s).join("\n")}\n"
116
+ end
117
+
118
+ def checksum
119
+ Digest::SHA256.hexdigest(to_s)
105
120
  end
106
121
 
107
122
  def exposed_ports
@@ -63,6 +63,10 @@ module Kuby
63
63
  stack.delete(name)
64
64
  layers.delete(name)
65
65
  end
66
+
67
+ def includes?(name)
68
+ layers.include?(name)
69
+ end
66
70
  end
67
71
  end
68
72
  end
@@ -33,6 +33,10 @@ module Kuby
33
33
  def timestamp_tags
34
34
  tags.map { |t| TimestampTag.try_parse(t) }.compact
35
35
  end
36
+
37
+ def latest_timestamp_tag
38
+ @latest_timestamp_tag ||= timestamp_tags.sort.last
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -36,28 +36,6 @@ module Kuby
36
36
  @tags.empty? ? default_tags : @tags
37
37
  end
38
38
 
39
- def tag
40
- t = ENV.fetch('KUBY_DOCKER_TAG') do
41
- environment.docker.tags.latest_timestamp_tag
42
- end
43
-
44
- unless t
45
- raise MissingTagError, 'could not find latest timestamped tag'
46
- end
47
-
48
- t.to_s
49
- end
50
-
51
- def previous_tag(current_tag)
52
- t = environment.docker.tags.previous_timestamp_tag(current_tag)
53
-
54
- unless t
55
- raise MissingTagError, 'could not find previous timestamped tag'
56
- end
57
-
58
- t.to_s
59
- end
60
-
61
39
  def distro
62
40
  @distro || DEFAULT_DISTRO
63
41
  end
@@ -3,13 +3,14 @@ module Kuby
3
3
  class SetupPhase < Layer
4
4
  DEFAULT_WORKING_DIR = '/usr/src/app'.freeze
5
5
 
6
- attr_accessor :base_image, :working_dir
6
+ attr_accessor :base_image, :working_dir, :rails_env
7
7
 
8
8
  def apply_to(dockerfile)
9
9
  dockerfile.from(base_image || default_base_image)
10
10
  dockerfile.workdir(working_dir || DEFAULT_WORKING_DIR)
11
- dockerfile.env("RAILS_ENV=#{Kuby.env}")
11
+ dockerfile.env("RAILS_ENV=#{rails_env || Kuby.env}")
12
12
  dockerfile.env("KUBY_ENV=#{Kuby.env}")
13
+ dockerfile.arg('RAILS_MASTER_KEY')
13
14
  end
14
15
 
15
16
  private
@@ -22,15 +22,15 @@ module Kuby
22
22
  end
23
23
 
24
24
  def bundler_version(version)
25
- bundler_phase.bundler_version = version
25
+ bundler_phase.version = version
26
26
  end
27
27
 
28
28
  def gemfile(path)
29
29
  bundler_phase.gemfile = path
30
30
  end
31
31
 
32
- def package(pkg)
33
- package_phase << pkg
32
+ def package(*args)
33
+ package_phase.add(*args)
34
34
  end
35
35
 
36
36
  def distro(distro_name)
@@ -62,6 +62,10 @@ module Kuby
62
62
  layer_stack.delete(*args)
63
63
  end
64
64
 
65
+ def exists?(*args)
66
+ layer_stack.includes?(*args)
67
+ end
68
+
65
69
  def credentials(&block)
66
70
  @credentials ||= Credentials.new
67
71
  @credentials.instance_eval(&block) if block
@@ -106,8 +110,26 @@ module Kuby
106
110
  @metadata ||= Metadata.new(environment)
107
111
  end
108
112
 
109
- def tags
110
- @tags ||= Tags.new(cli, remote_client, metadata)
113
+ def tag
114
+ t = ENV.fetch('KUBY_DOCKER_TAG') do
115
+ tags.latest_timestamp_tag
116
+ end
117
+
118
+ unless t
119
+ raise MissingTagError, 'could not find latest timestamped tag'
120
+ end
121
+
122
+ t.to_s
123
+ end
124
+
125
+ def previous_tag(current_tag)
126
+ t = tags.previous_timestamp_tag(current_tag)
127
+
128
+ unless t
129
+ raise MissingTagError, 'could not find previous timestamped tag'
130
+ end
131
+
132
+ t.to_s
111
133
  end
112
134
 
113
135
  def cli
@@ -129,6 +151,10 @@ module Kuby
129
151
  end
130
152
  end
131
153
 
154
+ def tags
155
+ @tags ||= Tags.new(cli, remote_client, metadata)
156
+ end
157
+
132
158
  private
133
159
 
134
160
  def layer_stack
@@ -8,7 +8,12 @@ module Kuby
8
8
  end
9
9
 
10
10
  def docker(&block)
11
- @docker ||= Docker::Spec.new(self)
11
+ @docker ||= if development?
12
+ Docker::DevSpec.new(self)
13
+ else
14
+ Docker::Spec.new(self)
15
+ end
16
+
12
17
  @docker.instance_eval(&block) if block
13
18
  @docker
14
19
  end
@@ -22,5 +27,9 @@ module Kuby
22
27
  def app_name
23
28
  definition.app_name
24
29
  end
30
+
31
+ def development?
32
+ name == 'development'
33
+ end
25
34
  end
26
35
  end
@@ -2,14 +2,14 @@ require 'kuby/kubernetes/errors'
2
2
 
3
3
  module Kuby
4
4
  module Kubernetes
5
- autoload :MinikubeProvider, 'kuby/kubernetes/minikube_provider'
6
- autoload :Deployer, 'kuby/kubernetes/deployer'
7
- autoload :DeployTask, 'kuby/kubernetes/deploy_task'
8
- autoload :DockerConfig, 'kuby/kubernetes/docker_config'
9
- autoload :Manifest, 'kuby/kubernetes/manifest'
10
- autoload :Plugins, 'kuby/kubernetes/plugins'
11
- autoload :Provider, 'kuby/kubernetes/provider'
12
- autoload :RegistrySecret, 'kuby/kubernetes/registry_secret'
13
- autoload :Spec, 'kuby/kubernetes/spec'
5
+ autoload :Deployer, 'kuby/kubernetes/deployer'
6
+ autoload :DeployTask, 'kuby/kubernetes/deploy_task'
7
+ autoload :DockerConfig, 'kuby/kubernetes/docker_config'
8
+ autoload :DockerDesktopProvider, 'kuby/kubernetes/docker_desktop_provider'
9
+ autoload :Manifest, 'kuby/kubernetes/manifest'
10
+ autoload :Plugins, 'kuby/kubernetes/plugins'
11
+ autoload :Provider, 'kuby/kubernetes/provider'
12
+ autoload :RegistrySecret, 'kuby/kubernetes/registry_secret'
13
+ autoload :Spec, 'kuby/kubernetes/spec'
14
14
  end
15
15
  end
@@ -18,6 +18,10 @@ module Kuby
18
18
  end
19
19
  end
20
20
 
21
+ def logger
22
+ deploy_task.instance_variable_get(:@logger)
23
+ end
24
+
21
25
  private
22
26
 
23
27
  def with_env(new_env)
@@ -6,22 +6,42 @@ module Kuby
6
6
  module Kubernetes
7
7
  class Deployer
8
8
  attr_reader :environment
9
+ attr_accessor :logdev
9
10
 
10
11
  def initialize(environment)
11
12
  @environment = environment
12
13
  end
13
14
 
14
15
  def deploy
15
- namespaced, global = all_resources.partition do |resource|
16
- # Unfortunately we can't use respond_to here because all KubeDSL
17
- # objects use ObjectMeta, which has a namespace field. Not sure
18
- # why, since it makes no sense for a namespace to have a namespace.
19
- # Instead we just check for nil here.
20
- resource.metadata.namespace
16
+ restart_rails_deployment_if_necessary do
17
+ namespaced, global = all_resources.partition do |resource|
18
+ # Unfortunately we can't use respond_to here because all KubeDSL
19
+ # objects use ObjectMeta, which has a namespace field. Not sure
20
+ # why, since it makes no sense for a namespace to have a namespace.
21
+ # Instead we just check for nil here.
22
+ resource.metadata.namespace
23
+ end
24
+
25
+ deploy_global_resources(global)
26
+ deploy_namespaced_resources(namespaced)
21
27
  end
28
+ end
29
+
30
+ # adhere to the "CLI" interface
31
+ def with_pipes(out = STDOUT, err = STDERR)
32
+ previous_logdev = logdev
33
+ @logdev = err
34
+ yield
35
+ ensure
36
+ @logdev = previous_logdev
37
+ end
38
+
39
+ def logdev
40
+ @logdev || STDERR
41
+ end
22
42
 
23
- deploy_global_resources(global)
24
- deploy_namespaced_resources(namespaced)
43
+ def last_status
44
+ nil
25
45
  end
26
46
 
27
47
  private
@@ -67,22 +87,54 @@ module Kuby
67
87
  filenames: [tmpdir]
68
88
  )
69
89
 
90
+ task.logger.reopen(logdev)
91
+
70
92
  task.run!(verify_result: true, prune: false)
71
93
  ensure
72
94
  ENV['KUBECONFIG'] = old_kubeconfig
73
95
  FileUtils.rm_rf(tmpdir)
74
96
  end
75
97
 
98
+ def restart_rails_deployment_if_necessary
99
+ deployed_image = nil
100
+ current_image = "#{docker.metadata.image_url}:#{docker.tag}"
101
+
102
+ if rails_app = kubernetes.plugin(:rails_app)
103
+ deployment_name = rails_app.deployment.metadata.name
104
+
105
+ deployment = cli.get_object(
106
+ 'deployment', namespace.metadata.name, deployment_name
107
+ )
108
+
109
+ deployed_image = deployment.dig(*%w(spec template spec containers), 0, 'image')
110
+ end
111
+
112
+ yield
113
+
114
+ if deployed_image == current_image
115
+ Kuby.logger.info('Docker image URL did not change, restarting Rails deployment manually')
116
+ cli.restart_deployment(namespace.metadata.name, deployment_name)
117
+ end
118
+ end
119
+
76
120
  def provider
77
- environment.kubernetes.provider
121
+ kubernetes.provider
78
122
  end
79
123
 
80
124
  def namespace
81
- environment.kubernetes.namespace
125
+ kubernetes.namespace
82
126
  end
83
127
 
84
128
  def all_resources
85
- environment.kubernetes.resources
129
+ kubernetes.resources
130
+ end
131
+
132
+ def docker
133
+ environment.docker
134
+ end
135
+
136
+ def kubernetes
137
+ environment.kubernetes
86
138
  end
87
139
 
88
140
  def cli
@@ -2,7 +2,7 @@ require 'kube-dsl'
2
2
 
3
3
  module Kuby
4
4
  module Kubernetes
5
- class MinikubeProvider < Provider
5
+ class DockerDesktopProvider < Provider
6
6
  STORAGE_CLASS_NAME = 'hostpath'.freeze
7
7
 
8
8
  class Config
@@ -20,9 +20,9 @@ module Kuby
20
20
  def after_configuration
21
21
  if rails_app = spec.plugin(:rails_app)
22
22
  # Remove ingress and change service type from ClusterIP to
23
- # LoadBalancer. No need to set up ingress for minikube since
24
- # it handles all the localhost mapping, etc if you set up a
25
- # service LB.
23
+ # LoadBalancer. No need to set up ingress for Docker Desktop
24
+ # since it handles all the localhost mapping, etc if you set
25
+ # up a service LB.
26
26
  rails_app.resources.delete(rails_app.ingress)
27
27
  rails_app.service.spec { type 'LoadBalancer' }
28
28
  end
@@ -55,20 +55,24 @@ module Kuby
55
55
  @kubernetes_cli ||= ::KubernetesCLI.new(kubeconfig_path)
56
56
  end
57
57
 
58
+ def helm_cli
59
+ @helm_cli ||= ::HelmCLI.new(kubeconfig_path)
60
+ end
61
+
58
62
  def kubeconfig_path
59
63
  raise NotImplementedError, "please define #{__method__} in #{self.class.name}"
60
64
  end
61
65
 
66
+ def deployer
67
+ @deployer ||= Kuby::Kubernetes::Deployer.new(environment)
68
+ end
69
+
62
70
  private
63
71
 
64
72
  def after_initialize
65
73
  # override this in derived classes
66
74
  end
67
75
 
68
- def deployer
69
- @deployer ||= Kuby::Kubernetes::Deployer.new(environment)
70
- end
71
-
72
76
  def spec
73
77
  environment.kubernetes
74
78
  end
@@ -36,13 +36,12 @@ module Kuby
36
36
  end
37
37
 
38
38
  def configure_plugin(plugin_name, &block)
39
- if @plugins[plugin_name] || plugin_klass = Kuby.plugins[plugin_name]
40
- @plugins[plugin_name] ||= plugin_klass.new(environment)
41
- @plugins[plugin_name].configure(&block) if block
42
- else
43
- raise MissingPluginError, "no plugin registered with name #{plugin_name}, "\
44
- 'do you need to add a gem to your Gemfile?'
39
+ unless @plugins.include?(plugin_name)
40
+ plugin_klass = Kuby.plugins.find(plugin_name)
41
+ @plugins[plugin_name] = plugin_klass.new(environment)
45
42
  end
43
+
44
+ @plugins[plugin_name].configure(&block) if block
46
45
  end
47
46
 
48
47
  alias_method :add_plugin, :configure_plugin
@@ -57,7 +56,7 @@ module Kuby
57
56
  end
58
57
 
59
58
  def before_deploy
60
- @tag ||= docker.metadata.tag
59
+ @tag ||= docker.tag
61
60
 
62
61
  provider.before_deploy(resources)
63
62
  @plugins.each { |_, plg| plg.before_deploy(resources) }
@@ -66,7 +65,7 @@ module Kuby
66
65
  end
67
66
 
68
67
  def after_deploy
69
- @tag ||= docker.metadata.tag
68
+ @tag ||= docker.tag
70
69
 
71
70
  @plugins.each { |_, plg| plg.after_deploy(resources) }
72
71
  provider.after_deploy(resources)
@@ -129,22 +128,24 @@ module Kuby
129
128
  def registry_secret(&block)
130
129
  spec = self
131
130
 
132
- @registry_secret ||= RegistrySecret.new do
133
- metadata do
134
- name "#{spec.selector_app}-registry-secret"
135
- namespace spec.namespace.metadata.name
136
- end
131
+ unless environment.development?
132
+ @registry_secret ||= RegistrySecret.new do
133
+ metadata do
134
+ name "#{spec.selector_app}-registry-secret"
135
+ namespace spec.namespace.metadata.name
136
+ end
137
137
 
138
- docker_config do
139
- registry_host spec.docker.metadata.image_host
140
- username spec.docker.credentials.username
141
- password spec.docker.credentials.password
142
- email spec.docker.credentials.email
138
+ docker_config do
139
+ registry_host spec.docker.metadata.image_host
140
+ username spec.docker.credentials.username
141
+ password spec.docker.credentials.password
142
+ email spec.docker.credentials.email
143
+ end
143
144
  end
144
- end
145
145
 
146
- @registry_secret.instance_eval(&block) if block
147
- @registry_secret
146
+ @registry_secret.instance_eval(&block) if block
147
+ @registry_secret
148
+ end
148
149
  end
149
150
 
150
151
  def resources
@@ -152,7 +153,7 @@ module Kuby
152
153
  namespace,
153
154
  registry_secret,
154
155
  *@plugins.flat_map { |_, plugin| plugin.resources }
155
- ])
156
+ ].compact)
156
157
  end
157
158
 
158
159
  def selector_app