kuby-core 0.11.16 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -2
- data/kuby-core.gemspec +1 -1
- data/lib/kuby.rb +2 -20
- data/lib/kuby/commands.rb +13 -67
- data/lib/kuby/docker.rb +27 -25
- data/lib/kuby/docker/alpine.rb +2 -1
- data/lib/kuby/docker/app_image.rb +19 -0
- data/lib/kuby/docker/cli.rb +4 -12
- data/lib/kuby/docker/docker_uri.rb +18 -7
- data/lib/kuby/docker/errors.rb +1 -19
- data/lib/kuby/docker/image.rb +115 -0
- data/lib/kuby/docker/layer.rb +0 -7
- data/lib/kuby/docker/local_tags.rb +9 -10
- data/lib/kuby/docker/package_phase.rb +0 -5
- data/lib/kuby/docker/packages.rb +1 -0
- data/lib/kuby/docker/remote_tags.rb +10 -5
- data/lib/kuby/docker/setup_phase.rb +17 -9
- data/lib/kuby/docker/spec.rb +29 -62
- data/lib/kuby/docker/timestamp_tag.rb +8 -1
- data/lib/kuby/docker/timestamped_image.rb +113 -0
- data/lib/kuby/environment.rb +1 -10
- data/lib/kuby/kubernetes/bare_metal_provider.rb +16 -4
- data/lib/kuby/kubernetes/deployer.rb +1 -1
- data/lib/kuby/kubernetes/docker_desktop_provider.rb +0 -15
- data/lib/kuby/kubernetes/spec.rb +21 -17
- data/lib/kuby/plugin.rb +2 -2
- data/lib/kuby/plugins/rails_app.rb +1 -0
- data/lib/kuby/plugins/rails_app/assets.rb +60 -70
- data/lib/kuby/plugins/rails_app/assets_image.rb +55 -0
- data/lib/kuby/plugins/rails_app/plugin.rb +53 -236
- data/lib/kuby/tasks.rb +30 -69
- data/lib/kuby/version.rb +1 -1
- data/spec/docker/spec_spec.rb +9 -118
- data/spec/docker/timestamped_image_spec.rb +123 -0
- data/spec/spec_helper.rb +10 -11
- metadata +9 -10
- data/lib/kuby/dev_setup.rb +0 -346
- data/lib/kuby/docker/dev_spec.rb +0 -202
- data/lib/kuby/docker/metadata.rb +0 -90
- data/lib/kuby/docker/tags.rb +0 -92
- data/lib/kuby/rails_commands.rb +0 -84
- data/spec/docker/metadata_spec.rb +0 -73
@@ -9,8 +9,15 @@ module Kuby
|
|
9
9
|
|
10
10
|
FORMAT = T.let('%Y%m%d%H%M%S'.freeze, String)
|
11
11
|
|
12
|
-
sig { params(str: String).returns(T.nilable(TimestampTag)) }
|
12
|
+
sig { params(str: T.nilable(String)).returns(T.nilable(TimestampTag)) }
|
13
13
|
def self.try_parse(str)
|
14
|
+
return nil unless str
|
15
|
+
|
16
|
+
# The strptime function stops scanning after the pattern has been matched, so
|
17
|
+
# we check for all numbers here to prevent things like 20210424165405-assets
|
18
|
+
# from being treated as a timestamp tag.
|
19
|
+
return nil unless str =~ /\A\d+\z/
|
20
|
+
|
14
21
|
time = begin
|
15
22
|
Time.strptime(str, FORMAT)
|
16
23
|
rescue ArgumentError
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'docker/remote'
|
4
|
+
|
5
|
+
module Kuby
|
6
|
+
module Docker
|
7
|
+
class TimestampedImage < Image
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig {
|
11
|
+
params(
|
12
|
+
dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
|
13
|
+
image_url: String,
|
14
|
+
credentials: Credentials,
|
15
|
+
main_tag: T.nilable(String),
|
16
|
+
alias_tags: T::Array[String]
|
17
|
+
).void
|
18
|
+
}
|
19
|
+
def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
|
20
|
+
@new_version = T.let(@new_version, T.nilable(Image))
|
21
|
+
@current_version = T.let(@current_version, T.nilable(Image))
|
22
|
+
@previous_version = T.let(@previous_version, T.nilable(Image))
|
23
|
+
|
24
|
+
@remote_client = T.let(@remote_client, T.nilable(::Docker::Remote::Client))
|
25
|
+
@local = T.let(@local, T.nilable(LocalTags))
|
26
|
+
@remote = T.let(@remote, T.nilable(RemoteTags))
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(Image) }
|
32
|
+
def new_version
|
33
|
+
@new_version ||= duplicate_with_tags(
|
34
|
+
TimestampTag.new(Time.now).to_s, [Kuby::Docker::LATEST_TAG]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
sig { returns(Image) }
|
39
|
+
def current_version
|
40
|
+
@current_version ||= duplicate_with_tags(
|
41
|
+
latest_timestamp_tag.to_s, [Kuby::Docker::LATEST_TAG]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { params(current_tag: T.nilable(String)).returns(Image) }
|
46
|
+
def previous_version(current_tag = nil)
|
47
|
+
@previous_version ||= duplicate_with_tags(
|
48
|
+
previous_timestamp_tag(current_tag).to_s, []
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { params(current_tag: T.nilable(String)).returns(TimestampTag) }
|
53
|
+
def previous_timestamp_tag(current_tag = nil)
|
54
|
+
current_tag = TimestampTag.try_parse(current_tag || latest_timestamp_tag.to_s)
|
55
|
+
raise MissingTagError, 'could not find current timestamp tag' unless current_tag
|
56
|
+
|
57
|
+
all_tags = timestamp_tags.sort
|
58
|
+
|
59
|
+
idx = all_tags.index do |tag|
|
60
|
+
tag.time == current_tag.time
|
61
|
+
end
|
62
|
+
|
63
|
+
idx ||= 0
|
64
|
+
raise MissingTagError, 'could not find previous timestamp tag' unless idx > 0
|
65
|
+
|
66
|
+
T.must(all_tags[idx - 1])
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { returns(TimestampTag) }
|
70
|
+
def latest_timestamp_tag
|
71
|
+
tag = timestamp_tags.sort.last
|
72
|
+
raise MissingTagError, 'could not find latest timestamp tag' unless tag
|
73
|
+
tag
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(build_args: T::Hash[String, String]).void }
|
77
|
+
def build(build_args = {})
|
78
|
+
docker_cli.build(new_version, build_args: build_args)
|
79
|
+
@current_version = new_version
|
80
|
+
@new_version = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { params(tag: String).void }
|
84
|
+
def push(tag)
|
85
|
+
docker_cli.push(image_url, tag)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
sig { returns(::Docker::Remote::Client) }
|
91
|
+
def remote_client
|
92
|
+
@remote_client ||= ::Docker::Remote::Client.new(
|
93
|
+
image_host, image_repo, credentials.username, credentials.password,
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { returns(T::Array[TimestampTag]) }
|
98
|
+
def timestamp_tags
|
99
|
+
(local.timestamp_tags + remote.timestamp_tags).uniq
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { returns(LocalTags) }
|
103
|
+
def local
|
104
|
+
@local ||= LocalTags.new(docker_cli, image_url)
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { returns(RemoteTags) }
|
108
|
+
def remote
|
109
|
+
@remote ||= RemoteTags.new(remote_client, image_url)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/kuby/environment.rb
CHANGED
@@ -12,12 +12,7 @@ module Kuby
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def docker(&block)
|
15
|
-
@docker ||=
|
16
|
-
Docker::DevSpec.new(self)
|
17
|
-
else
|
18
|
-
Docker::Spec.new(self)
|
19
|
-
end
|
20
|
-
|
15
|
+
@docker ||= Docker::Spec.new(self)
|
21
16
|
@docker.instance_eval(&block) if block
|
22
17
|
@docker
|
23
18
|
end
|
@@ -31,9 +26,5 @@ module Kuby
|
|
31
26
|
def app_name
|
32
27
|
definition.app_name
|
33
28
|
end
|
34
|
-
|
35
|
-
def development?
|
36
|
-
name == 'development'
|
37
|
-
end
|
38
29
|
end
|
39
30
|
end
|
@@ -1,10 +1,13 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
|
+
|
2
3
|
require 'kube-dsl'
|
3
4
|
|
4
5
|
module Kuby
|
5
6
|
module Kubernetes
|
6
7
|
class BareMetalProvider < Provider
|
7
|
-
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
STORAGE_CLASS_NAME = T.let('hostpath'.freeze, String)
|
8
11
|
|
9
12
|
class Config
|
10
13
|
extend ::KubeDSL::ValueFields
|
@@ -12,25 +15,34 @@ module Kuby
|
|
12
15
|
value_fields :kubeconfig
|
13
16
|
end
|
14
17
|
|
18
|
+
sig { returns(Config) }
|
15
19
|
attr_reader :config
|
16
20
|
|
21
|
+
sig { params(environment: Environment).void }
|
22
|
+
def initialize(environment)
|
23
|
+
@config = T.let(Config.new, Config)
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(block: T.proc.void).void }
|
17
28
|
def configure(&block)
|
18
29
|
config.instance_eval(&block) if block
|
19
30
|
end
|
20
31
|
|
32
|
+
sig { returns(String) }
|
21
33
|
def kubeconfig_path
|
22
34
|
config.kubeconfig
|
23
35
|
end
|
24
36
|
|
37
|
+
sig { returns(String) }
|
25
38
|
def storage_class_name
|
26
39
|
STORAGE_CLASS_NAME
|
27
40
|
end
|
28
41
|
|
29
42
|
private
|
30
43
|
|
44
|
+
sig { void }
|
31
45
|
def after_initialize
|
32
|
-
@config = Config.new
|
33
|
-
|
34
46
|
configure do
|
35
47
|
# default kubeconfig path
|
36
48
|
kubeconfig File.join(ENV['HOME'], '.kube', 'config')
|
@@ -102,7 +102,7 @@ module Kuby
|
|
102
102
|
|
103
103
|
def restart_rails_deployment_if_necessary
|
104
104
|
deployed_image = nil
|
105
|
-
current_image = "#{docker.
|
105
|
+
current_image = "#{docker.image.image_url}:#{kubernetes.tag}"
|
106
106
|
|
107
107
|
if rails_app = kubernetes.plugin(:rails_app)
|
108
108
|
deployment_name = rails_app.deployment.metadata.name
|
@@ -18,21 +18,6 @@ module Kuby
|
|
18
18
|
config.instance_eval(&block) if block
|
19
19
|
end
|
20
20
|
|
21
|
-
def after_configuration
|
22
|
-
if rails_app = spec.plugin(:rails_app)
|
23
|
-
# Remove ingress and change service type from ClusterIP to
|
24
|
-
# LoadBalancer. No need to set up ingress for Docker Desktop
|
25
|
-
# since it handles all the localhost mapping, etc if you set
|
26
|
-
# up a service LB.
|
27
|
-
rails_app.resources.delete(rails_app.ingress)
|
28
|
-
rails_app.service.spec { type 'LoadBalancer' }
|
29
|
-
end
|
30
|
-
|
31
|
-
if assets = spec.plugin(:rails_assets)
|
32
|
-
assets.service.spec { type 'LoadBalancer' }
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
21
|
def kubeconfig_path
|
37
22
|
config.kubeconfig
|
38
23
|
end
|
data/lib/kuby/kubernetes/spec.rb
CHANGED
@@ -72,7 +72,7 @@ module Kuby
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def before_deploy
|
75
|
-
@tag ||= docker.
|
75
|
+
@tag ||= docker.image.current_version.main_tag
|
76
76
|
|
77
77
|
provider.before_deploy(resources)
|
78
78
|
@plugins.each { |_, plg| plg.before_deploy(resources) }
|
@@ -81,7 +81,7 @@ module Kuby
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def after_deploy
|
84
|
-
@tag ||= docker.
|
84
|
+
@tag ||= docker.image.current_version.main_tag
|
85
85
|
|
86
86
|
@plugins.each { |_, plg| plg.after_deploy(resources) }
|
87
87
|
provider.after_deploy(resources)
|
@@ -144,24 +144,22 @@ module Kuby
|
|
144
144
|
def registry_secret(&block)
|
145
145
|
spec = self
|
146
146
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
namespace spec.namespace.metadata.name
|
152
|
-
end
|
153
|
-
|
154
|
-
docker_config do
|
155
|
-
registry_host spec.docker.metadata.image_hostname
|
156
|
-
username spec.docker.credentials.username
|
157
|
-
password spec.docker.credentials.password
|
158
|
-
email spec.docker.credentials.email
|
159
|
-
end
|
147
|
+
@registry_secret ||= RegistrySecret.new do
|
148
|
+
metadata do
|
149
|
+
name "#{spec.selector_app}-registry-secret"
|
150
|
+
namespace spec.namespace.metadata.name
|
160
151
|
end
|
161
152
|
|
162
|
-
|
163
|
-
|
153
|
+
docker_config do
|
154
|
+
registry_host spec.docker.image.image_hostname
|
155
|
+
username spec.docker.image.credentials.username
|
156
|
+
password spec.docker.image.credentials.password
|
157
|
+
email spec.docker.image.credentials.email
|
158
|
+
end
|
164
159
|
end
|
160
|
+
|
161
|
+
@registry_secret.instance_eval(&block) if block
|
162
|
+
@registry_secret
|
165
163
|
end
|
166
164
|
|
167
165
|
def resources
|
@@ -172,6 +170,12 @@ module Kuby
|
|
172
170
|
].compact)
|
173
171
|
end
|
174
172
|
|
173
|
+
def docker_images
|
174
|
+
@docker_images ||= [
|
175
|
+
docker.image, *@plugins.flat_map { |_, plugin| plugin.docker_images }
|
176
|
+
]
|
177
|
+
end
|
178
|
+
|
175
179
|
def selector_app
|
176
180
|
@selector_app ||= environment.app_name.downcase
|
177
181
|
end
|
data/lib/kuby/plugin.rb
CHANGED
@@ -3,6 +3,7 @@ module Kuby
|
|
3
3
|
module Plugins
|
4
4
|
module RailsApp
|
5
5
|
autoload :AssetCopyTask, 'kuby/plugins/rails_app/asset_copy_task'
|
6
|
+
autoload :AssetsImage, 'kuby/plugins/rails_app/assets_image'
|
6
7
|
autoload :Assets, 'kuby/plugins/rails_app/assets'
|
7
8
|
autoload :Database, 'kuby/plugins/rails_app/database'
|
8
9
|
autoload :MySQL, 'kuby/plugins/rails_app/mysql'
|
@@ -47,38 +47,6 @@ module Kuby
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
def configure_deployment(deployment, docker_image)
|
51
|
-
spec = self
|
52
|
-
|
53
|
-
deployment.spec.template.spec do
|
54
|
-
init_container(:copy_assets) do
|
55
|
-
name "#{spec.selector_app}-copy-assets"
|
56
|
-
command %w(bundle exec rake kuby:rails_app:assets:copy)
|
57
|
-
image docker_image
|
58
|
-
|
59
|
-
volume_mount do
|
60
|
-
name 'assets'
|
61
|
-
mount_path RAILS_MOUNT_PATH
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
container(:web) do
|
66
|
-
volume_mount do
|
67
|
-
name 'assets'
|
68
|
-
mount_path NGINX_MOUNT_PATH
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
volume do
|
73
|
-
name 'assets'
|
74
|
-
|
75
|
-
persistent_volume_claim do
|
76
|
-
claim_name spec.volume_claim.metadata.name
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
50
|
def copy_task
|
83
51
|
@copy_task ||= AssetCopyTask.new(
|
84
52
|
from: asset_path, to: RAILS_MOUNT_PATH
|
@@ -238,7 +206,7 @@ module Kuby
|
|
238
206
|
container(:nginx) do
|
239
207
|
name "#{kube_spec.selector_app}-#{kube_spec.role}"
|
240
208
|
image_pull_policy 'IfNotPresent'
|
241
|
-
image
|
209
|
+
image "#{kube_spec.image.image_url}:#{kube_spec.kubernetes.tag || Kuby::Docker::LATEST_TAG}-assets"
|
242
210
|
|
243
211
|
port do
|
244
212
|
container_port NGINX_PORT
|
@@ -252,11 +220,6 @@ module Kuby
|
|
252
220
|
sub_path 'nginx.conf'
|
253
221
|
end
|
254
222
|
|
255
|
-
volume_mount do
|
256
|
-
name 'assets'
|
257
|
-
mount_path NGINX_MOUNT_PATH
|
258
|
-
end
|
259
|
-
|
260
223
|
readiness_probe do
|
261
224
|
success_threshold 1
|
262
225
|
failure_threshold 2
|
@@ -272,6 +235,10 @@ module Kuby
|
|
272
235
|
end
|
273
236
|
end
|
274
237
|
|
238
|
+
image_pull_secret do
|
239
|
+
name kube_spec.environment.kubernetes.registry_secret.metadata.name
|
240
|
+
end
|
241
|
+
|
275
242
|
volume do
|
276
243
|
name 'nginx-config'
|
277
244
|
|
@@ -280,14 +247,6 @@ module Kuby
|
|
280
247
|
end
|
281
248
|
end
|
282
249
|
|
283
|
-
volume do
|
284
|
-
name 'assets'
|
285
|
-
|
286
|
-
persistent_volume_claim do
|
287
|
-
claim_name kube_spec.volume_claim.metadata.name
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
250
|
restart_policy 'Always'
|
292
251
|
service_account_name kube_spec.service_account.metadata.name
|
293
252
|
end
|
@@ -299,38 +258,19 @@ module Kuby
|
|
299
258
|
@deployment
|
300
259
|
end
|
301
260
|
|
302
|
-
def volume_claim
|
303
|
-
spec = self
|
304
|
-
|
305
|
-
@volume_claim ||= KubeDSL.persistent_volume_claim do
|
306
|
-
metadata do
|
307
|
-
name "#{spec.selector_app}-#{spec.role}"
|
308
|
-
namespace spec.namespace.metadata.name
|
309
|
-
end
|
310
|
-
|
311
|
-
spec do
|
312
|
-
access_modes ['ReadWriteOnce']
|
313
|
-
storage_class_name spec.environment.kubernetes.provider.storage_class_name
|
314
|
-
|
315
|
-
resources do
|
316
|
-
requests do
|
317
|
-
add :storage, '10Gi'
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
|
324
261
|
def resources
|
325
262
|
@resources ||= [
|
326
263
|
service,
|
327
264
|
service_account,
|
328
265
|
nginx_config,
|
329
|
-
deployment
|
330
|
-
volume_claim
|
266
|
+
deployment
|
331
267
|
]
|
332
268
|
end
|
333
269
|
|
270
|
+
def docker_images
|
271
|
+
@docker_images ||= [docker_spec]
|
272
|
+
end
|
273
|
+
|
334
274
|
def namespace
|
335
275
|
environment.kubernetes.namespace
|
336
276
|
end
|
@@ -342,6 +282,56 @@ module Kuby
|
|
342
282
|
def role
|
343
283
|
ROLE
|
344
284
|
end
|
285
|
+
|
286
|
+
def docker
|
287
|
+
environment.docker
|
288
|
+
end
|
289
|
+
|
290
|
+
def kubernetes
|
291
|
+
environment.kubernetes
|
292
|
+
end
|
293
|
+
|
294
|
+
def docker_images
|
295
|
+
@docker_images ||= [image]
|
296
|
+
end
|
297
|
+
|
298
|
+
def image
|
299
|
+
@image ||= RailsApp::AssetsImage.new(docker.image, -> { dockerfile })
|
300
|
+
end
|
301
|
+
|
302
|
+
private
|
303
|
+
|
304
|
+
def dockerfile
|
305
|
+
Docker::Dockerfile.new.tap do |df|
|
306
|
+
base_image = docker.image.current_version
|
307
|
+
cur_tag = base_image.main_tag
|
308
|
+
app_name = environment.app_name.downcase
|
309
|
+
|
310
|
+
tags = begin
|
311
|
+
[base_image.previous_timestamp_tag(cur_tag).to_s, cur_tag]
|
312
|
+
rescue Kuby::Docker::MissingTagError
|
313
|
+
[cur_tag, nil]
|
314
|
+
end
|
315
|
+
|
316
|
+
# this can handle more than 2 tags by virtue of using each_cons :)
|
317
|
+
tags.each_cons(2) do |prev_tag, tag|
|
318
|
+
prev_image_name = "#{app_name}-#{prev_tag}"
|
319
|
+
df.from("#{base_image.image_url}:#{prev_tag}", as: prev_image_name)
|
320
|
+
df.run("mkdir -p #{RAILS_MOUNT_PATH}")
|
321
|
+
df.run("bundle exec rake kuby:rails_app:assets:copy")
|
322
|
+
|
323
|
+
if tag
|
324
|
+
image_name = "#{app_name}-#{tag}"
|
325
|
+
df.from("#{base_image.image_url}:#{tag}", as: image_name)
|
326
|
+
df.copy("--from=#{prev_image_name} #{RAILS_MOUNT_PATH}", RAILS_MOUNT_PATH)
|
327
|
+
df.run("bundle exec rake kuby:rails_app:assets:copy")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
df.from(NGINX_IMAGE)
|
332
|
+
df.copy("--from=#{"#{app_name}-#{tags.compact.last}"} #{RAILS_MOUNT_PATH}", NGINX_MOUNT_PATH)
|
333
|
+
end
|
334
|
+
end
|
345
335
|
end
|
346
336
|
end
|
347
337
|
end
|