kuby-core 0.11.14 → 0.13.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/Gemfile +2 -2
  4. data/README.md +2 -1
  5. data/bin/kuby +2 -0
  6. data/kuby-core.gemspec +2 -2
  7. data/lib/kuby/basic_logger.rb +1 -1
  8. data/lib/kuby/cli_base.rb +9 -4
  9. data/lib/kuby/commands.rb +16 -69
  10. data/lib/kuby/docker/alpine.rb +2 -1
  11. data/lib/kuby/docker/app_image.rb +19 -0
  12. data/lib/kuby/docker/bundler_phase.rb +9 -3
  13. data/lib/kuby/docker/cli.rb +4 -12
  14. data/lib/kuby/docker/docker_uri.rb +18 -7
  15. data/lib/kuby/docker/errors.rb +1 -19
  16. data/lib/kuby/docker/image.rb +115 -0
  17. data/lib/kuby/docker/layer.rb +0 -7
  18. data/lib/kuby/docker/local_tags.rb +9 -10
  19. data/lib/kuby/docker/package_phase.rb +0 -5
  20. data/lib/kuby/docker/packages.rb +1 -0
  21. data/lib/kuby/docker/remote_tags.rb +10 -5
  22. data/lib/kuby/docker/setup_phase.rb +17 -9
  23. data/lib/kuby/docker/spec.rb +29 -62
  24. data/lib/kuby/docker/timestamp_tag.rb +8 -1
  25. data/lib/kuby/docker/timestamped_image.rb +113 -0
  26. data/lib/kuby/docker/yarn_phase.rb +2 -2
  27. data/lib/kuby/docker.rb +27 -25
  28. data/lib/kuby/environment.rb +1 -10
  29. data/lib/kuby/kubernetes/bare_metal_provider.rb +53 -0
  30. data/lib/kuby/kubernetes/deployer.rb +2 -1
  31. data/lib/kuby/kubernetes/docker_desktop_provider.rb +0 -15
  32. data/lib/kuby/kubernetes/spec.rb +21 -17
  33. data/lib/kuby/kubernetes.rb +1 -0
  34. data/lib/kuby/plugin.rb +2 -2
  35. data/lib/kuby/plugins/rails_app/assets.rb +60 -70
  36. data/lib/kuby/plugins/rails_app/assets_image.rb +55 -0
  37. data/lib/kuby/plugins/rails_app/plugin.rb +54 -213
  38. data/lib/kuby/plugins/rails_app.rb +1 -0
  39. data/lib/kuby/tasks.rb +30 -69
  40. data/lib/kuby/version.rb +1 -1
  41. data/lib/kuby.rb +3 -20
  42. data/spec/docker/spec_spec.rb +21 -118
  43. data/spec/docker/timestamped_image_spec.rb +123 -0
  44. data/spec/spec_helper.rb +10 -11
  45. metadata +11 -14
  46. data/lib/kuby/dev_setup.rb +0 -346
  47. data/lib/kuby/docker/dev_spec.rb +0 -202
  48. data/lib/kuby/docker/metadata.rb +0 -90
  49. data/lib/kuby/docker/tags.rb +0 -92
  50. data/lib/kuby/rails_commands.rb +0 -84
  51. data/spec/docker/metadata_spec.rb +0 -73
  52. data/spec/dummy/Gemfile.lock +0 -223
  53. data/spec/dummy/config/master.key +0 -1
  54. data/spec/dummy/tmp/cache/bootsnap-load-path-cache +0 -0
@@ -1,346 +0,0 @@
1
- # typed: strict
2
-
3
- module Kuby
4
- class Spinner
5
- extend T::Sig
6
-
7
- PIECES = T.let(%w(- \\ | /).freeze, T::Array[String])
8
- INTERVAL = T.let(0.2, Float) # seconds
9
-
10
- sig {
11
- params(
12
- message: String,
13
- block: T.proc.params(spinner: Spinner).void
14
- )
15
- .void
16
- }
17
- def self.spin(message, &block)
18
- yield new(message)
19
- end
20
-
21
- sig { returns(String) }
22
- attr_reader :message
23
-
24
- sig { returns(Symbol) }
25
- attr_reader :status
26
-
27
- sig { params(message: String).void }
28
- def initialize(message)
29
- @message = T.let(message, String)
30
- @status = T.let(:running, Symbol)
31
- @thread = T.let(Thread.new do
32
- counter = 0
33
-
34
- while true
35
- case status
36
- when :running
37
- piece = PIECES[counter % PIECES.size]
38
- STDOUT.write "\r[#{piece}] #{message}"
39
- sleep INTERVAL
40
- counter += 1
41
- when :success
42
- STDOUT.write("\r[+] #{message}")
43
- break
44
- when :failure
45
- STDOUT.write("\r[×] #{message}")
46
- break
47
- end
48
- end
49
-
50
- puts
51
- end, Thread)
52
- end
53
-
54
- sig { void }
55
- def success
56
- @status = :success
57
- @thread.join
58
- end
59
-
60
- sig { void }
61
- def failure
62
- @status = :failure
63
- @thread.join
64
- end
65
- end
66
-
67
- class SetupTask
68
- extend T::Sig
69
-
70
- sig { returns(String) }
71
- attr_reader :message
72
-
73
- sig { returns(T.proc.void) }
74
- attr_reader :callable
75
-
76
- sig { params(message: String, callable: T.proc.void).void }
77
- def initialize(message, callable)
78
- @message = message
79
- @callable = callable
80
- end
81
-
82
- sig { void }
83
- def run
84
- callable.call
85
- end
86
- end
87
-
88
- class Pipe
89
- extend T::Sig
90
-
91
- sig { returns(Symbol) }
92
- attr_reader :name
93
-
94
- sig { returns(T.untyped) }
95
- attr_reader :cli
96
-
97
- sig { returns(StringIO) }
98
- attr_reader :out
99
-
100
- sig { returns(StringIO) }
101
- attr_reader :err
102
-
103
- sig { params(name: Symbol, cli: T.untyped).void }
104
- def initialize(name, cli)
105
- @name = T.let(name, Symbol)
106
- @cli = T.let(cli, T.untyped)
107
- @out = T.let(StringIO.new, StringIO)
108
- @err = T.let(StringIO.new, StringIO)
109
- end
110
-
111
- sig { params(block: T.proc.void).void }
112
- def wrap(&block)
113
- cli.with_pipes(out, err) do
114
- block.call
115
- end
116
- end
117
-
118
- sig { returns(T::Boolean) }
119
- def success?
120
- cli.last_status.nil? || cli.last_status.success?
121
- end
122
- end
123
-
124
- class Pipes
125
- extend T::Sig
126
- extend T::Generic
127
-
128
- include Enumerable
129
-
130
- Elem = type_member(fixed: Pipe)
131
-
132
- sig { returns(T::Array[Pipe]) }
133
- attr_reader :pipes
134
-
135
- sig { returns(T.nilable(StandardError)) }
136
- attr_reader :ex
137
-
138
- sig { params(clis: T::Hash[Symbol, T.untyped]).returns(Pipes) }
139
- def self.build(clis)
140
- new(clis.map { |name, cli| Pipe.new(name, cli) })
141
- end
142
-
143
- sig { params(pipes: T::Array[Pipe]).void }
144
- def initialize(pipes)
145
- @ex = T.let(@ex, T.nilable(StandardError))
146
- @pipes = T.let(pipes, T::Array[Pipe])
147
- end
148
-
149
- sig {
150
- override.params(
151
- block: T.proc.params(package: Pipe).void
152
- )
153
- .void
154
- }
155
- def each(&block)
156
- pipes.each(&block)
157
- end
158
-
159
- sig { params(block: T.proc.void).void }
160
- def wrap(&block)
161
- do_wrap(pipes, &block)
162
- end
163
-
164
- sig { returns(T::Boolean) }
165
- def success?
166
- pipes.all?(&:success?) && !ex
167
- end
168
-
169
- private
170
-
171
- sig {
172
- params(
173
- remaining_pipes: T::Array[Pipe],
174
- block: T.proc.void
175
- )
176
- .void
177
- }
178
- def do_wrap(remaining_pipes, &block)
179
- if remaining_pipes.empty?
180
- begin
181
- yield
182
- rescue => e
183
- @ex = e
184
- end
185
-
186
- return
187
- end
188
-
189
- T.must(remaining_pipes[0]).wrap do
190
- do_wrap(T.must(remaining_pipes[1..-1]), &block)
191
- end
192
- end
193
- end
194
-
195
- class SetupTaskList
196
- extend T::Sig
197
-
198
- sig { returns(T::Array[SetupTask]) }
199
- attr_reader :tasks
200
-
201
- sig { returns T::Hash[Symbol, T.untyped] }
202
- attr_reader :clis
203
-
204
- sig { params(tasks: T::Array[SetupTask], clis: T::Hash[Symbol, T.untyped]).void }
205
- def initialize(tasks, clis)
206
- @tasks = tasks
207
- @clis = clis
208
- end
209
-
210
- sig { void }
211
- def run
212
- tasks.each do |task|
213
- pipes = Pipes.build(clis)
214
-
215
- Spinner.spin(task.message) do |spinner|
216
- pipes.wrap { task.run }
217
-
218
- if pipes.success?
219
- spinner.success
220
- else
221
- spinner.failure
222
- print_error(pipes.ex)
223
-
224
- pipes.each do |pipe|
225
- print_streams(pipe)
226
- end
227
-
228
- return false
229
- end
230
- end
231
- end
232
-
233
- true
234
- end
235
-
236
- private
237
-
238
- sig { params(pipe: Pipe).void }
239
- def print_streams(pipe)
240
- unless pipe.out.string.strip.empty?
241
- puts("========= #{pipe.name.upcase} STDOUT ========")
242
- puts pipe.out.string
243
- end
244
-
245
- unless pipe.err.string.strip.empty?
246
- puts("========= #{pipe.name.upcase} STDERR ========")
247
- puts pipe.err.string
248
- end
249
- end
250
-
251
- sig { params(ex: T.nilable(StandardError)).void }
252
- def print_error(ex)
253
- return unless ex
254
- puts("========= RUBY ERROR ========")
255
- puts(ex.message)
256
- puts(T.must(ex.backtrace).join("\n"))
257
- end
258
- end
259
-
260
- class DevSetup
261
- extend T::Sig
262
-
263
- sig { returns(Environment) }
264
- attr_reader :environment
265
-
266
- sig { params(environment: Environment).void }
267
- def initialize(environment)
268
- @environment = T.let(environment, Environment)
269
- @setup_tasks = T.let(@setup_tasks, T.nilable(T::Array[SetupTask]))
270
- @clis = T.let(@clis, T.nilable(T::Hash[Symbol, T.untyped]))
271
- @tasks = T.let(@tasks, T.nilable(Tasks))
272
- end
273
-
274
- sig { void }
275
- def run
276
- SetupTaskList.new(setup_tasks, clis).run
277
- end
278
-
279
- private
280
-
281
- sig { returns(T::Array[SetupTask]) }
282
- def setup_tasks
283
- @setup_tasks ||= [
284
- SetupTask.new(
285
- 'Building dev Docker image', -> { tasks.build }
286
- ),
287
-
288
- SetupTask.new(
289
- 'Setting up local Kubernetes cluster', -> { tasks.setup }
290
- ),
291
-
292
- SetupTask.new(
293
- 'Deploying dev environment', -> { tasks.deploy }
294
- ),
295
-
296
- SetupTask.new(
297
- 'Installing bundler', -> {
298
- tasks.remote_system("gem install bundler -v #{Bundler::VERSION}")
299
- }
300
- ),
301
-
302
- SetupTask.new(
303
- 'Installing bundled gems', -> { tasks.remote_system('bundle install') }
304
- ),
305
-
306
- SetupTask.new(
307
- 'Installing Javascript packages', -> { tasks.remote_system('yarn install') }
308
- ),
309
-
310
- SetupTask.new(
311
- 'Creating database', -> { tasks.remote_system('bundle exec rake db:create') }
312
- ),
313
-
314
- SetupTask.new(
315
- 'Migrating database', -> { tasks.remote_system('bundle exec rake db:migrate') }
316
- )
317
- ]
318
- end
319
-
320
- sig { returns(Kubernetes::Spec) }
321
- def kubernetes
322
- environment.kubernetes
323
- end
324
-
325
- sig { returns(Docker::Spec) }
326
- def docker
327
- environment.docker
328
- end
329
-
330
- sig { returns T::Hash[Symbol, T.untyped] }
331
- def clis
332
- @clis ||= {
333
- kubectl: kubernetes.provider.kubernetes_cli,
334
- helm: kubernetes.provider.helm_cli,
335
- krane: kubernetes.provider.deployer,
336
- docker: docker.cli,
337
- kuby: Kuby.logger
338
- }
339
- end
340
-
341
- sig { returns(Tasks) }
342
- def tasks
343
- @tasks ||= Kuby::Tasks.new(environment)
344
- end
345
- end
346
- end
@@ -1,202 +0,0 @@
1
- # typed: strict
2
-
3
- module Kuby
4
- module Docker
5
- class WebserverDevPhase < Layer
6
- extend T::Sig
7
-
8
- DEFAULT_PORT = T.let(3000, Integer)
9
-
10
- sig { params(port: Integer).void }
11
- attr_writer :port
12
-
13
- sig { params(environment: Environment).void }
14
- def initialize(environment)
15
- super
16
-
17
- @port = T.let(@port, T.nilable(Integer))
18
- end
19
-
20
- sig { override.params(dockerfile: Dockerfile).void }
21
- def apply_to(dockerfile)
22
- dockerfile.expose(port)
23
- end
24
-
25
- sig { returns(Integer) }
26
- def port
27
- @port || DEFAULT_PORT
28
- end
29
- end
30
-
31
- class DevSpec
32
- extend T::Sig
33
-
34
- sig { returns(Environment) }
35
- attr_reader :environment
36
-
37
- sig { params(environment: Environment).void }
38
- def initialize(environment)
39
- @environment = environment
40
-
41
- @setup_phase = T.let(@setup_phase, T.nilable(SetupPhase))
42
- @package_phase = T.let(@package_phase, T.nilable(PackagePhase))
43
- @webserver_phase = T.let(@webserver_phase, T.nilable(WebserverDevPhase))
44
- @metadata = T.let(@metadata, T.nilable(Metadata))
45
- @distro_spec = T.let(@distro_spec, T.nilable(Distro))
46
- @cli = T.let(@cli, T.nilable(CLI))
47
- @tags = T.let(@tags, T.nilable(LocalTags))
48
- @layer_stack = T.let(@layer_stack, T.nilable(Docker::LayerStack))
49
- end
50
-
51
- sig { params(dir: String).void }
52
- def working_dir(dir)
53
- setup_phase.working_dir = dir
54
- end
55
-
56
- sig { params(env: String).void }
57
- def rails_env(env)
58
- setup_phase.rails_env = env
59
- end
60
-
61
- sig {
62
- params(
63
- package_name: Symbol,
64
- version: T.nilable(String)
65
- )
66
- .void
67
- }
68
- def package(package_name, version = nil)
69
- package_phase.add(package_name, version)
70
- end
71
-
72
- sig { params(distro_name: Symbol).void }
73
- def distro(distro_name)
74
- metadata.distro = distro_name
75
- @distro_spec = nil
76
- end
77
-
78
- sig { params(port: Integer).void }
79
- def port(port)
80
- webserver_phase.port = port
81
- end
82
-
83
- sig {
84
- params(
85
- name: Symbol,
86
- layer: T.nilable(Layer),
87
- block: T.nilable(T.proc.params(df: Dockerfile).void)
88
- )
89
- .void
90
- }
91
- def use(name, layer = nil, &block)
92
- layer_stack.use(name, layer, &block)
93
- end
94
-
95
- sig {
96
- params(
97
- name: Symbol,
98
- layer: T.nilable(T.any(Layer, T::Hash[Symbol, T.untyped])),
99
- options: T::Hash[Symbol, T.untyped],
100
- block: T.nilable(T.proc.params(df: Dockerfile).void)
101
- )
102
- .void
103
- }
104
- def insert(name, layer = nil, options = {}, &block)
105
- layer_stack.insert(name, layer, options, &block)
106
- end
107
-
108
- sig { params(name: Symbol).void }
109
- def delete(name)
110
- layer_stack.delete(name)
111
- end
112
-
113
- sig { params(name: Symbol).returns(T::Boolean) }
114
- def exists?(name)
115
- layer_stack.includes?(name)
116
- end
117
-
118
- sig {
119
- params(block: T.nilable(T.proc.void)).returns(Credentials)
120
- }
121
- def credentials(&block)
122
- raise 'Docker credentials are not supported in the development environment'
123
- end
124
-
125
- sig { returns(Dockerfile) }
126
- def to_dockerfile
127
- Dockerfile.new.tap do |df|
128
- layer_stack.each { |layer| layer.apply_to(df) }
129
- df.cmd("#{distro_spec.shell_exe} -c 'while test 1; do sleep 5; done'")
130
- end
131
- end
132
-
133
- sig { returns(SetupPhase) }
134
- def setup_phase
135
- @setup_phase ||= SetupPhase.new(environment)
136
- end
137
-
138
- sig { returns(PackagePhase) }
139
- def package_phase
140
- @package_phase ||= PackagePhase.new(environment)
141
- end
142
-
143
- sig { returns(WebserverDevPhase) }
144
- def webserver_phase
145
- @webserver_phase ||= WebserverDevPhase.new(environment)
146
- end
147
-
148
- sig { returns(Metadata) }
149
- def metadata
150
- @metadata ||= Metadata.new(environment)
151
- end
152
-
153
- sig { returns(Distro) }
154
- def distro_spec
155
- @distro_spec ||= if distro_klass = Kuby.distros[metadata.distro]
156
- distro_klass.new(self)
157
- else
158
- raise MissingDistroError, "distro '#{metadata.distro}' hasn't been registered"
159
- end
160
- end
161
-
162
- sig { returns(String) }
163
- def tag
164
- t = ENV.fetch('KUBY_DOCKER_TAG') do
165
- tags.latest_timestamp_tag
166
- end
167
-
168
- unless t
169
- raise MissingTagError, 'could not find latest timestamped tag'
170
- end
171
-
172
- t.to_s
173
- end
174
-
175
- sig { params(current_tag: String).returns(String) }
176
- def previous_tag(current_tag)
177
- raise MissingTagError, 'cannot roll back in the development environment'
178
- end
179
-
180
- sig { returns(CLI) }
181
- def cli
182
- @cli ||= Docker::CLI.new
183
- end
184
-
185
- private
186
-
187
- sig { returns(LocalTags) }
188
- def tags
189
- @tags ||= LocalTags.new(cli, metadata)
190
- end
191
-
192
- sig { returns(Docker::LayerStack) }
193
- def layer_stack
194
- @layer_stack ||= Docker::LayerStack.new.tap do |stack|
195
- stack.use(:setup_phase, setup_phase)
196
- stack.use(:package_phase, package_phase)
197
- stack.use(:webserver_phase, webserver_phase)
198
- end
199
- end
200
- end
201
- end
202
- end
@@ -1,90 +0,0 @@
1
- # typed: strict
2
-
3
- require 'uri'
4
-
5
- module Kuby
6
- module Docker
7
- class Metadata
8
- extend T::Sig
9
-
10
- DEFAULT_DISTRO = :debian
11
- LATEST_TAG = T.let('latest'.freeze, String)
12
-
13
- sig { params(image_url: String).void }
14
- attr_writer :image_url
15
-
16
- sig { returns(Environment) }
17
- attr_reader :environment
18
-
19
- sig { params(environment: Environment).void }
20
- def initialize(environment)
21
- @environment = environment
22
- @tags = T.let([], T::Array[String])
23
-
24
- @image_url = T.let(@image_url, T.nilable(String))
25
- @image_host = T.let(@image_host, T.nilable(String))
26
- @image_hostname = T.let(@image_hostname, T.nilable(String))
27
- @image_repo = T.let(@image_repo, T.nilable(String))
28
- @distro = T.let(@distro, T.nilable(Symbol))
29
- @full_image_uri = T.let(@full_image_uri, T.nilable(DockerURI))
30
- @default_image_url = T.let(@default_image_url, T.nilable(String))
31
- @default_tags = T.let(@default_tags, T.nilable(T::Array[String]))
32
- end
33
-
34
- sig { returns(String) }
35
- def image_url
36
- @image_url || default_image_url
37
- end
38
-
39
- sig { returns(String) }
40
- def image_host
41
- @image_host ||= "#{full_image_uri.host}:#{full_image_uri.port}"
42
- end
43
-
44
- sig { returns(String) }
45
- def image_hostname
46
- @image_hostname ||= full_image_uri.host
47
- end
48
-
49
- sig { returns(String) }
50
- def image_repo
51
- @image_repo ||= full_image_uri.path
52
- end
53
-
54
- sig { returns(T::Array[String]) }
55
- def tags
56
- @tags.empty? ? default_tags : @tags
57
- end
58
-
59
- sig { returns(Symbol) }
60
- def distro
61
- @distro || DEFAULT_DISTRO
62
- end
63
-
64
- sig { params(distro_name: Symbol).void }
65
- def distro=(distro_name)
66
- @distro = distro_name
67
- end
68
-
69
- private
70
-
71
- sig { returns(DockerURI) }
72
- def full_image_uri
73
- @full_image_uri ||= DockerURI.parse(image_url)
74
- end
75
-
76
- sig { returns(String) }
77
- def default_image_url
78
- # assuming dockerhub by not specifying full url
79
- @default_image_url ||= environment.app_name.downcase
80
- end
81
-
82
- sig { returns(T::Array[String]) }
83
- def default_tags
84
- @default_tags ||= [
85
- TimestampTag.new(Time.now).to_s, LATEST_TAG
86
- ]
87
- end
88
- end
89
- end
90
- end
@@ -1,92 +0,0 @@
1
- # typed: strict
2
-
3
- module Kuby
4
- module Docker
5
- class Tags
6
- extend T::Sig
7
-
8
- LATEST = Metadata::LATEST_TAG
9
-
10
- sig { returns(CLI) }
11
- attr_reader :cli
12
-
13
- sig { returns(::Docker::Remote::Client) }
14
- attr_reader :remote_client
15
-
16
- sig { returns Metadata }
17
- attr_reader :metadata
18
-
19
- sig {
20
- params(
21
- cli: CLI,
22
- remote_client: ::Docker::Remote::Client,
23
- metadata: Metadata
24
- )
25
- .void
26
- }
27
- def initialize(cli, remote_client, metadata)
28
- @cli = cli
29
- @remote_client = remote_client
30
- @metadata = metadata
31
-
32
- @local = T.let(@local, T.nilable(LocalTags))
33
- @remote = T.let(@remote, T.nilable(RemoteTags))
34
- @latest_timestamp_tag = T.let(@latest_timestamp_tag, T.nilable(TimestampTag))
35
- end
36
-
37
- sig { returns(T::Array[String]) }
38
- def tags
39
- (local.tags + remote.tags).uniq
40
- end
41
-
42
- sig { returns(T::Array[String]) }
43
- def latest_tags
44
- (local.latest_tags + remote.latest_tags).uniq
45
- end
46
-
47
- sig {
48
- params(current_tag: String).returns(T.nilable(TimestampTag))
49
- }
50
- def previous_timestamp_tag(current_tag)
51
- current_tag = TimestampTag.try_parse(current_tag)
52
- return nil unless current_tag
53
-
54
- all_tags = timestamp_tags.sort
55
-
56
- idx = all_tags.index do |tag|
57
- tag.time == current_tag.time
58
- end
59
-
60
- idx ||= 0
61
- return nil unless idx > 0
62
-
63
- all_tags[idx - 1]
64
- end
65
-
66
- sig { returns(T::Array[TimestampTag]) }
67
- def timestamp_tags
68
- (local.timestamp_tags + remote.timestamp_tags).uniq
69
- end
70
-
71
- sig { returns(T.nilable(TimestampTag)) }
72
- def latest_timestamp_tag
73
- @latest_timestamp_tag ||= timestamp_tags.sort.last
74
- end
75
-
76
- sig { returns(T.self_type) }
77
- def all
78
- self
79
- end
80
-
81
- sig { returns(LocalTags) }
82
- def local
83
- @local ||= LocalTags.new(cli, metadata)
84
- end
85
-
86
- sig { returns(RemoteTags) }
87
- def remote
88
- @remote ||= RemoteTags.new(remote_client, metadata)
89
- end
90
- end
91
- end
92
- end