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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -1
- data/README.md +11 -1
- data/bin/kuby +4 -0
- data/kuby-core.gemspec +5 -2
- data/lib/kuby.rb +46 -18
- data/lib/kuby/basic_logger.rb +13 -0
- data/lib/kuby/cli_base.rb +81 -8
- data/lib/kuby/commands.rb +220 -0
- data/lib/kuby/definition.rb +1 -3
- data/lib/kuby/dev_setup.rb +255 -0
- data/lib/kuby/docker.rb +1 -0
- data/lib/kuby/docker/bundler_phase.rb +3 -3
- data/lib/kuby/docker/cli.rb +13 -1
- data/lib/kuby/docker/dev_spec.rb +131 -0
- data/lib/kuby/docker/dockerfile.rb +16 -1
- data/lib/kuby/docker/layer_stack.rb +4 -0
- data/lib/kuby/docker/local_tags.rb +4 -0
- data/lib/kuby/docker/metadata.rb +0 -22
- data/lib/kuby/docker/setup_phase.rb +3 -2
- data/lib/kuby/docker/spec.rb +31 -5
- data/lib/kuby/environment.rb +10 -1
- data/lib/kuby/kubernetes.rb +9 -9
- data/lib/kuby/kubernetes/deploy_task.rb +4 -0
- data/lib/kuby/kubernetes/deployer.rb +63 -11
- data/lib/kuby/kubernetes/{minikube_provider.rb → docker_desktop_provider.rb} +4 -4
- data/lib/kuby/kubernetes/provider.rb +8 -4
- data/lib/kuby/kubernetes/spec.rb +23 -22
- data/lib/kuby/plugin_registry.rb +27 -0
- data/lib/kuby/plugins/rails_app/generators/kuby.rb +3 -15
- data/lib/kuby/plugins/rails_app/plugin.rb +230 -40
- data/lib/kuby/rails_commands.rb +89 -0
- data/lib/kuby/railtie.rb +0 -4
- data/lib/kuby/tasks.rb +76 -23
- data/lib/kuby/version.rb +1 -1
- data/spec/docker/metadata_spec.rb +0 -108
- data/spec/docker/spec_spec.rb +266 -0
- data/spec/spec_helper.rb +8 -1
- metadata +44 -9
- data/lib/kuby/tasks/kuby.rake +0 -70
data/lib/kuby/definition.rb
CHANGED
@@ -9,9 +9,7 @@ module Kuby
|
|
9
9
|
def environment(name = Kuby.env, &block)
|
10
10
|
name = name.to_s
|
11
11
|
|
12
|
-
|
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
|
data/lib/kuby/docker.rb
CHANGED
@@ -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(
|
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
|
|
data/lib/kuby/docker/cli.rb
CHANGED
@@ -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
|