kuby-core 0.7.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -0
  3. data/Gemfile +1 -0
  4. data/README.md +11 -1
  5. data/bin/kuby +4 -0
  6. data/kuby-core.gemspec +9 -4
  7. data/lib/kuby.rb +58 -19
  8. data/lib/kuby/basic_logger.rb +13 -0
  9. data/lib/kuby/cli_base.rb +81 -8
  10. data/lib/kuby/commands.rb +244 -0
  11. data/lib/kuby/definition.rb +1 -11
  12. data/lib/kuby/dev_setup.rb +255 -0
  13. data/lib/kuby/docker.rb +1 -0
  14. data/lib/kuby/docker/bundler_phase.rb +3 -3
  15. data/lib/kuby/docker/cli.rb +13 -1
  16. data/lib/kuby/docker/dev_spec.rb +131 -0
  17. data/lib/kuby/docker/dockerfile.rb +16 -1
  18. data/lib/kuby/docker/layer.rb +4 -4
  19. data/lib/kuby/docker/layer_stack.rb +4 -0
  20. data/lib/kuby/docker/local_tags.rb +4 -0
  21. data/lib/kuby/docker/metadata.rb +18 -38
  22. data/lib/kuby/docker/package_phase.rb +2 -2
  23. data/lib/kuby/docker/setup_phase.rb +3 -2
  24. data/lib/kuby/docker/spec.rb +42 -16
  25. data/lib/kuby/docker/timestamp_tag.rb +6 -0
  26. data/lib/kuby/environment.rb +15 -2
  27. data/lib/kuby/kubernetes.rb +9 -11
  28. data/lib/kuby/kubernetes/deploy_task.rb +4 -1
  29. data/lib/kuby/kubernetes/deployer.rb +70 -15
  30. data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +8 -4
  31. data/lib/kuby/kubernetes/provider.rb +12 -8
  32. data/lib/kuby/kubernetes/spec.rb +30 -29
  33. data/lib/kuby/plugin.rb +59 -0
  34. data/lib/kuby/plugin_registry.rb +27 -0
  35. data/lib/kuby/plugins.rb +6 -0
  36. data/lib/kuby/plugins/nginx_ingress.rb +71 -0
  37. data/lib/kuby/plugins/rails_app.rb +18 -0
  38. data/lib/kuby/plugins/rails_app/asset_copy_task.rb +117 -0
  39. data/lib/kuby/plugins/rails_app/assets.rb +347 -0
  40. data/lib/kuby/plugins/rails_app/database.rb +74 -0
  41. data/lib/kuby/{kubernetes/plugins → plugins}/rails_app/generators/kuby.rb +14 -16
  42. data/lib/kuby/plugins/rails_app/mysql.rb +152 -0
  43. data/lib/kuby/plugins/rails_app/plugin.rb +593 -0
  44. data/lib/kuby/plugins/rails_app/postgres.rb +143 -0
  45. data/lib/kuby/plugins/rails_app/rewrite_db_config.rb +11 -0
  46. data/lib/kuby/plugins/rails_app/sqlite.rb +32 -0
  47. data/lib/kuby/plugins/rails_app/tasks.rake +36 -0
  48. data/lib/kuby/rails_commands.rb +89 -0
  49. data/lib/kuby/railtie.rb +0 -4
  50. data/lib/kuby/tasks.rb +85 -31
  51. data/lib/kuby/version.rb +1 -1
  52. data/spec/docker/metadata_spec.rb +84 -0
  53. data/spec/docker/spec_spec.rb +266 -0
  54. data/spec/docker/timestamp_tag_spec.rb +54 -4
  55. data/spec/dummy/Gemfile +54 -0
  56. data/spec/dummy/Gemfile.lock +223 -0
  57. data/spec/dummy/README.md +24 -0
  58. data/spec/dummy/Rakefile +6 -0
  59. data/spec/dummy/app/assets/config/manifest.js +2 -0
  60. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  61. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  62. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  63. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  64. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  65. data/spec/dummy/app/javascript/channels/consumer.js +6 -0
  66. data/spec/dummy/app/javascript/channels/index.js +5 -0
  67. data/spec/dummy/app/javascript/packs/application.js +17 -0
  68. data/spec/dummy/app/jobs/application_job.rb +7 -0
  69. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  70. data/spec/dummy/app/models/application_record.rb +3 -0
  71. data/spec/dummy/app/views/layouts/application.html.erb +15 -0
  72. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  73. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  74. data/spec/dummy/bin/bundle +114 -0
  75. data/spec/dummy/bin/rails +9 -0
  76. data/spec/dummy/bin/rake +9 -0
  77. data/spec/dummy/bin/setup +36 -0
  78. data/spec/dummy/bin/spring +17 -0
  79. data/spec/dummy/bin/yarn +11 -0
  80. data/spec/dummy/config.ru +5 -0
  81. data/spec/dummy/config/application.rb +19 -0
  82. data/spec/dummy/config/boot.rb +4 -0
  83. data/spec/dummy/config/cable.yml +10 -0
  84. data/spec/dummy/config/credentials.yml.enc +1 -0
  85. data/spec/dummy/config/database.yml +25 -0
  86. data/spec/dummy/config/environment.rb +5 -0
  87. data/spec/dummy/config/environments/development.rb +62 -0
  88. data/spec/dummy/config/environments/production.rb +112 -0
  89. data/spec/dummy/config/environments/test.rb +49 -0
  90. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  91. data/spec/dummy/config/initializers/assets.rb +14 -0
  92. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  93. data/spec/dummy/config/initializers/content_security_policy.rb +30 -0
  94. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  95. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  96. data/spec/dummy/config/initializers/inflections.rb +16 -0
  97. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  98. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  99. data/spec/dummy/config/locales/en.yml +33 -0
  100. data/spec/dummy/config/master.key +1 -0
  101. data/spec/dummy/config/puma.rb +38 -0
  102. data/spec/dummy/config/routes.rb +3 -0
  103. data/spec/dummy/config/spring.rb +6 -0
  104. data/spec/dummy/config/storage.yml +34 -0
  105. data/spec/dummy/db/seeds.rb +7 -0
  106. data/spec/dummy/package.json +11 -0
  107. data/spec/dummy/public/404.html +67 -0
  108. data/spec/dummy/public/422.html +67 -0
  109. data/spec/dummy/public/500.html +66 -0
  110. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  111. data/spec/dummy/public/apple-touch-icon.png +0 -0
  112. data/spec/dummy/public/favicon.ico +0 -0
  113. data/spec/dummy/public/robots.txt +1 -0
  114. data/spec/dummy/test/application_system_test_case.rb +5 -0
  115. data/spec/dummy/test/channels/application_cable/connection_test.rb +11 -0
  116. data/spec/dummy/test/test_helper.rb +13 -0
  117. data/spec/dummy/tmp/cache/bootsnap-load-path-cache +0 -0
  118. data/spec/spec_helper.rb +77 -2
  119. data/spec/support/docker/fake_cli.rb +54 -0
  120. data/spec/support/docker/remote/fake_client.rb +16 -0
  121. data/spec/trailing_hash_spec.rb +23 -0
  122. metadata +139 -30
  123. data/lib/ext/krane/kubernetes_resource.rb +0 -16
  124. data/lib/kuby/kubernetes/plugin.rb +0 -55
  125. data/lib/kuby/kubernetes/plugins.rb +0 -8
  126. data/lib/kuby/kubernetes/plugins/nginx_ingress.rb +0 -73
  127. data/lib/kuby/kubernetes/plugins/rails_app.rb +0 -16
  128. data/lib/kuby/kubernetes/plugins/rails_app/database.rb +0 -79
  129. data/lib/kuby/kubernetes/plugins/rails_app/mysql.rb +0 -154
  130. data/lib/kuby/kubernetes/plugins/rails_app/plugin.rb +0 -379
  131. data/lib/kuby/kubernetes/plugins/rails_app/postgres.rb +0 -142
  132. data/lib/kuby/kubernetes/plugins/rails_app/rewrite_db_config.rb +0 -13
  133. data/lib/kuby/kubernetes/plugins/rails_app/sqlite.rb +0 -30
  134. data/lib/kuby/kubernetes/plugins/rails_app/tasks.rake +0 -28
  135. 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)
@@ -20,14 +18,6 @@ module Kuby
20
18
  environments[name]
21
19
  end
22
20
 
23
- def docker(&block)
24
- environment.docker(&block)
25
- end
26
-
27
- def kubernetes(&block)
28
- environment.kubernetes(&block)
29
- end
30
-
31
21
  def environments
32
22
  @environments ||= {}
33
23
  end
@@ -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