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
@@ -9,9 +9,7 @@ module Kuby
9
9
  def environment(name = Kuby.env, &block)
10
10
  name = name.to_s
11
11
 
12
- if name
13
- environments[name] ||= Environment.new(name, self)
14
- end
12
+ environments[name] ||= Environment.new(name, self)
15
13
 
16
14
  if block_given?
17
15
  environments[name].instance_eval(&block)
@@ -0,0 +1,255 @@
1
+ module Kuby
2
+ class Spinner
3
+ PIECES = %w(- \\ | /).freeze
4
+ INTERVAL = 0.2 # seconds
5
+
6
+ def self.spin(message)
7
+ yield new(message)
8
+ end
9
+
10
+ attr_reader :message, :status
11
+
12
+ def initialize(message)
13
+ @message = message
14
+ @status = :running
15
+ @thread = Thread.new do
16
+ counter = 0
17
+
18
+ while true
19
+ case status
20
+ when :running
21
+ piece = PIECES[counter % PIECES.size]
22
+ STDOUT.write "\r[#{piece}] #{message}"
23
+ sleep INTERVAL
24
+ counter += 1
25
+ when :success
26
+ STDOUT.write("\r[+] #{message}")
27
+ break
28
+ when :failure
29
+ STDOUT.write("\r[×] #{message}")
30
+ break
31
+ end
32
+ end
33
+
34
+ puts
35
+ end
36
+ end
37
+
38
+ def success
39
+ @status = :success
40
+ @thread.join
41
+ end
42
+
43
+ def failure
44
+ @status = :failure
45
+ @thread.join
46
+ end
47
+ end
48
+
49
+ class SetupTask
50
+ attr_reader :message, :callable
51
+
52
+ def initialize(message, callable)
53
+ @message = message
54
+ @callable = callable
55
+ end
56
+
57
+ def run
58
+ callable.call
59
+ end
60
+ end
61
+
62
+ class Pipe
63
+ attr_reader :name, :cli, :out, :err
64
+
65
+ def initialize(name, cli)
66
+ @name = name
67
+ @cli = cli
68
+ @out = StringIO.new
69
+ @err = StringIO.new
70
+ end
71
+
72
+ def wrap(&block)
73
+ cli.with_pipes(out, err) do
74
+ block.call
75
+ end
76
+ end
77
+
78
+ def success?
79
+ cli.last_status.nil? || cli.last_status.success?
80
+ end
81
+ end
82
+
83
+ class Pipes
84
+ include Enumerable
85
+
86
+ attr_reader :pipes, :ex
87
+
88
+ def self.build(clis)
89
+ new(clis.map { |name, cli| Pipe.new(name, cli) })
90
+ end
91
+
92
+ def initialize(pipes)
93
+ @pipes = pipes
94
+ end
95
+
96
+ def each(&block)
97
+ pipes.each(&block)
98
+ end
99
+
100
+ def wrap(&block)
101
+ do_wrap(pipes, &block)
102
+ end
103
+
104
+ def success?
105
+ pipes.all?(&:success?) && !ex
106
+ end
107
+
108
+ private
109
+
110
+ def do_wrap(remaining_pipes, &block)
111
+ if remaining_pipes.empty?
112
+ begin
113
+ yield
114
+ rescue => e
115
+ @ex = e
116
+ end
117
+
118
+ return
119
+ end
120
+
121
+ remaining_pipes[0].wrap do
122
+ do_wrap(remaining_pipes[1..-1], &block)
123
+ end
124
+ end
125
+ end
126
+
127
+ class SetupTaskList
128
+ attr_reader :tasks, :clis
129
+
130
+ def initialize(tasks, clis)
131
+ @tasks = tasks
132
+ @clis = clis
133
+ end
134
+
135
+ def run
136
+ tasks.each do |task|
137
+ pipes = Pipes.build(clis)
138
+
139
+ Spinner.spin(task.message) do |spinner|
140
+ pipes.wrap { task.run }
141
+
142
+ if pipes.success?
143
+ spinner.success
144
+ else
145
+ spinner.failure
146
+ print_error(pipes.ex)
147
+
148
+ pipes.each do |pipe|
149
+ print_streams(pipe)
150
+ end
151
+
152
+ return false
153
+ end
154
+ end
155
+ end
156
+
157
+ true
158
+ end
159
+
160
+ private
161
+
162
+ def print_streams(pipe)
163
+ unless pipe.out.string.strip.empty?
164
+ puts("========= #{pipe.name.upcase} STDOUT ========")
165
+ puts pipe.out.string
166
+ end
167
+
168
+ unless pipe.err.string.strip.empty?
169
+ puts("========= #{pipe.name.upcase} STDERR ========")
170
+ puts pipe.err.string
171
+ end
172
+ end
173
+
174
+ def print_error(ex)
175
+ return unless ex
176
+ puts("========= RUBY ERROR ========")
177
+ puts(ex.message)
178
+ puts(ex.backtrace.join("\n"))
179
+ end
180
+ end
181
+
182
+ class DevSetup
183
+ attr_reader :environment
184
+
185
+ def initialize(environment)
186
+ @environment = environment
187
+ end
188
+
189
+ def run
190
+ SetupTaskList.new(setup_tasks, clis).run
191
+ end
192
+
193
+ private
194
+
195
+ def setup_tasks
196
+ @setup_tasks ||= [
197
+ SetupTask.new(
198
+ 'Building dev Docker image', -> { tasks.build }
199
+ ),
200
+
201
+ SetupTask.new(
202
+ 'Setting up local Kubernetes cluster', -> { tasks.setup }
203
+ ),
204
+
205
+ SetupTask.new(
206
+ 'Deploying dev environment', -> { tasks.deploy }
207
+ ),
208
+
209
+ SetupTask.new(
210
+ 'Installing bundler', -> {
211
+ tasks.remote_system("gem install bundler -v #{Bundler::VERSION}")
212
+ }
213
+ ),
214
+
215
+ SetupTask.new(
216
+ 'Installing bundled gems', -> { tasks.remote_system('bundle install') }
217
+ ),
218
+
219
+ SetupTask.new(
220
+ 'Installing Javascript packages', -> { tasks.remote_system('yarn install') }
221
+ ),
222
+
223
+ SetupTask.new(
224
+ 'Creating database', -> { tasks.remote_system('bundle exec rake db:create') }
225
+ ),
226
+
227
+ SetupTask.new(
228
+ 'Migrating database', -> { tasks.remote_system('bundle exec rake db:migrate') }
229
+ )
230
+ ]
231
+ end
232
+
233
+ def kubernetes
234
+ environment.kubernetes
235
+ end
236
+
237
+ def docker
238
+ environment.docker
239
+ end
240
+
241
+ def clis
242
+ @clis ||= {
243
+ kubectl: kubernetes.provider.kubernetes_cli,
244
+ helm: kubernetes.provider.helm_cli,
245
+ krane: kubernetes.provider.deployer,
246
+ docker: docker.cli,
247
+ kuby: Kuby.logger
248
+ }
249
+ end
250
+
251
+ def tasks
252
+ @tasks ||= Kuby::Tasks.new(environment)
253
+ end
254
+ end
255
+ end
@@ -9,6 +9,7 @@ module Kuby
9
9
  autoload :CopyPhase, 'kuby/docker/copy_phase'
10
10
  autoload :Credentials, 'kuby/docker/credentials'
11
11
  autoload :Debian, 'kuby/docker/debian'
12
+ autoload :DevSpec, 'kuby/docker/dev_spec'
12
13
  autoload :Dockerfile, 'kuby/docker/dockerfile'
13
14
  autoload :InlineLayer, 'kuby/docker/inline_layer'
14
15
  autoload :Layer, 'kuby/docker/layer'
@@ -13,14 +13,14 @@ module Kuby
13
13
  v = version || default_version
14
14
  wo = without || DEFAULT_WITHOUT
15
15
 
16
- dockerfile.run("gem install bundler -v #{v}")
16
+ dockerfile.run('gem', 'install', 'bundler', '-v', v)
17
17
 
18
18
  # bundle install
19
19
  dockerfile.copy(gf, '.')
20
20
  dockerfile.copy(lf, '.')
21
21
 
22
22
  # set bundle path so docker will cache the bundle
23
- dockerfile.run('mkdir ./bundle')
23
+ dockerfile.run('mkdir', './bundle')
24
24
  dockerfile.env('BUNDLE_PATH=./bundle')
25
25
 
26
26
  unless wo.empty?
@@ -35,7 +35,7 @@ module Kuby
35
35
  )
36
36
 
37
37
  # generate binstubs and add the bin directory to our path
38
- dockerfile.run('bundle binstubs --all')
38
+ dockerfile.run('bundle', 'binstubs', '--all')
39
39
  dockerfile.env("PATH=./bin:$PATH")
40
40
  end
41
41
 
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'open3'
3
+ require 'shellwords'
3
4
 
4
5
  module Kuby
5
6
  module Docker
@@ -42,10 +43,13 @@ module Kuby
42
43
  config.fetch('auths', {}).keys
43
44
  end
44
45
 
45
- def build(dockerfile:, image_url:, tags:)
46
+ def build(dockerfile:, image_url:, tags:, build_args: {})
46
47
  cmd = [
47
48
  executable, 'build',
48
49
  *tags.flat_map { |tag| ['-t', "#{image_url}:#{tag}"] },
50
+ *build_args.flat_map do |arg, val|
51
+ ['--build-arg', Shellwords.shellescape("#{arg}=#{val}")]
52
+ end,
49
53
  '-f-', '.'
50
54
  ]
51
55
 
@@ -99,6 +103,14 @@ module Kuby
99
103
  def status_key
100
104
  :kuby_docker_cli_last_status
101
105
  end
106
+
107
+ def stdout_key
108
+ :kuby_docker_stdout
109
+ end
110
+
111
+ def stderr_key
112
+ :kuby_docker_stderr
113
+ end
102
114
  end
103
115
  end
104
116
  end
@@ -0,0 +1,131 @@
1
+ module Kuby
2
+ module Docker
3
+ class WebserverDevPhase < Layer
4
+ DEFAULT_PORT = 3000
5
+
6
+ attr_accessor :port
7
+
8
+ def apply_to(dockerfile)
9
+ dockerfile.expose(port)
10
+ end
11
+
12
+ def port
13
+ @port || DEFAULT_PORT
14
+ end
15
+ end
16
+
17
+ class DevSpec
18
+ attr_reader :environment
19
+
20
+ def initialize(environment)
21
+ @environment = environment
22
+ end
23
+
24
+ def working_dir(dir)
25
+ setup_phase.working_dir = dir
26
+ end
27
+
28
+ def rails_env(env)
29
+ setup_phase.rails_env = env
30
+ end
31
+
32
+ def package(*args)
33
+ package_phase.add(*args)
34
+ end
35
+
36
+ def distro(distro_name)
37
+ metadata.distro = distro_name
38
+ @distro_spec = nil
39
+ end
40
+
41
+ def port(port)
42
+ webserver_phase.port = port
43
+ end
44
+
45
+ def use(*args, &block)
46
+ layer_stack.use(*args, &block)
47
+ end
48
+
49
+ def insert(*args, &block)
50
+ layer_stack.insert(*args, &block)
51
+ end
52
+
53
+ def delete(*args)
54
+ layer_stack.delete(*args)
55
+ end
56
+
57
+ def exists?(*args)
58
+ layer_stack.includes?(*args)
59
+ end
60
+
61
+ def to_dockerfile
62
+ Dockerfile.new.tap do |df|
63
+ layer_stack.each { |layer| layer.apply_to(df) }
64
+ df.cmd("#{distro_spec.shell_exe} -c 'while test 1; do sleep 5; done'")
65
+ end
66
+ end
67
+
68
+ def setup_phase
69
+ @setup_phase ||= SetupPhase.new(environment)
70
+ end
71
+
72
+ def package_phase
73
+ @package_phase ||= PackagePhase.new(environment)
74
+ end
75
+
76
+ def webserver_phase
77
+ @webserver_phase ||= WebserverDevPhase.new(environment)
78
+ end
79
+
80
+ def metadata
81
+ @metadata ||= Metadata.new(environment)
82
+ end
83
+
84
+ def cli
85
+ @cli ||= Docker::CLI.new
86
+ end
87
+
88
+ def distro_spec
89
+ @distro_spec ||= if distro_klass = Kuby.distros[metadata.distro]
90
+ distro_klass.new(self)
91
+ else
92
+ raise MissingDistroError, "distro '#{metadata.distro}' hasn't been registered"
93
+ end
94
+ end
95
+
96
+ def tag
97
+ t = ENV.fetch('KUBY_DOCKER_TAG') do
98
+ tags.latest_timestamp_tag
99
+ end
100
+
101
+ unless t
102
+ raise MissingTagError, 'could not find latest timestamped tag'
103
+ end
104
+
105
+ t.to_s
106
+ end
107
+
108
+ def previous_tag(*)
109
+ raise MissingTagError, 'cannot roll back in the development environment'
110
+ end
111
+
112
+ def cli
113
+ @cli ||= Docker::CLI.new
114
+ end
115
+
116
+ private
117
+
118
+ def tags
119
+ @tags ||= LocalTags.new(cli, metadata)
120
+ end
121
+
122
+ def layer_stack
123
+ @layer_stack ||= LayerStack.new.tap do |stack|
124
+ stack.use(:setup_phase, setup_phase)
125
+ stack.use(:package_phase, package_phase)
126
+ stack.use(:webserver_phase, webserver_phase)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end