kuby-core 0.11.16 → 0.12.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 +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
|