kuby-core 0.14.0 → 0.17.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 +29 -0
- data/lib/kuby/commands.rb +51 -15
- data/lib/kuby/docker/app_image.rb +2 -10
- data/lib/kuby/docker/bundler_phase.rb +9 -2
- data/lib/kuby/docker/cli.rb +10 -3
- data/lib/kuby/docker/docker_uri.rb +35 -5
- data/lib/kuby/docker/image.rb +28 -5
- data/lib/kuby/docker/spec.rb +10 -1
- data/lib/kuby/docker/timestamped_image.rb +22 -14
- data/lib/kuby/kubernetes/bare_metal_provider.rb +4 -2
- data/lib/kuby/kubernetes/spec.rb +16 -6
- data/lib/kuby/plugin.rb +1 -0
- data/lib/kuby/plugin_registry.rb +15 -0
- data/lib/kuby/plugins/rails_app/assets.rb +2 -1
- data/lib/kuby/plugins/rails_app/assets_image.rb +9 -6
- data/lib/kuby/plugins/rails_app/generators/kuby.rb +31 -9
- data/lib/kuby/plugins/rails_app/plugin.rb +8 -9
- data/lib/kuby/tasks.rb +128 -23
- data/lib/kuby/utils/table.rb +35 -0
- data/lib/kuby/utils.rb +5 -0
- data/lib/kuby/version.rb +1 -1
- data/spec/docker/timestamped_image_spec.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06ed279a1b0ecaac0daed1b868610bac67e6564611360de4cd841a24827438e2
|
4
|
+
data.tar.gz: f8616a5b0ab87c4fb71afa3afba0e001760b3c0855b7670b976013de3c1db5d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d75bf991f55a97798152449606740b2d94c9a012ad7c17ea0b9b940c9ca9b3aad0e31baa4635ffeeec584144492d967054cdbfffa386c05c7427f1e90ac58e8f
|
7
|
+
data.tar.gz: 73b7718181d0e43ab1382c70a4a22da1835da30c887c2f42bc67bcce15d811aa0efab93df3a5fd6d28e2b2ce6d6dc3a73154a4cacdadcfeb9641ca0b2ba5604c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,32 @@
|
|
1
|
+
## 0.17.1
|
2
|
+
* Allow storage class to be customized when using the built-in bare metal provider.
|
3
|
+
* Fix a bug where the assets image would be built using the previous app image instead of the current one.
|
4
|
+
* Centralize the lookup of the Rails master key (not user-facing).
|
5
|
+
* Add descriptions to numerous CLI commands and their arguments.
|
6
|
+
* CLI now accepts multiple `--only` flags passed to subcommands like `build`, `push`, etc.
|
7
|
+
* Allow running setup steps for individual plugins.
|
8
|
+
|
9
|
+
## 0.16.1
|
10
|
+
* Fix a few bugs caused by leaving `continue-on-error: true` in the GitHub actions config 🤦
|
11
|
+
- Docker builds would fail if no previous images existed in the registry.
|
12
|
+
- The assets image would fail to build because `RAILS_MASTER_KEY` was not propagated correctly.
|
13
|
+
* Use the kuby-prebundler gem in the integration tests instead of a custom layer.
|
14
|
+
|
15
|
+
## 0.16.0
|
16
|
+
* Allow Bundler executable to be customized.
|
17
|
+
- Main use-case is to allow adding in the Prebundler plugin, https://github.com/getkuby/kuby-prebundler.
|
18
|
+
* Print error message and exit on missing Docker build args.
|
19
|
+
- Ignore with `--ignore-missing-args`.
|
20
|
+
|
21
|
+
## 0.15.0
|
22
|
+
* Add an extra parameter to the Docker spec for specifying the URL of the Docker registry index.
|
23
|
+
- In most cases, the registry and registry index URLs will be the same. However Docker Hub, the default registry, uses index.docker.io for API requests (catalog, tags, etc) but only allows pushes to docker.io.
|
24
|
+
* Add config/master.key to the .dockerignore created by the Rails generator.
|
25
|
+
- This was an unfortunate oversight, but such are the perils of using pre-1.0 software.
|
26
|
+
* Print an error message if building images on non-x86 hardware.
|
27
|
+
- People with M1 Macs are running into mismatches between their laptop's arch and the arch of their hosting provider's hardware. The error message asks you to explicitly pass the `--platform` flag to Docker.
|
28
|
+
- We may need to revisit this in the future if ARM processors become more popular.
|
29
|
+
|
1
30
|
## 0.14.0
|
2
31
|
* Make RAILS_MASTER_KEY available in the assets image (@palkan, #63)
|
3
32
|
* Allow specifying custom docker build options via CLI (@palkan, #65)
|
data/lib/kuby/commands.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: false
|
2
2
|
|
3
3
|
require 'kuby/version'
|
4
4
|
require 'gli'
|
@@ -62,10 +62,22 @@ module Kuby
|
|
62
62
|
true
|
63
63
|
end
|
64
64
|
|
65
|
-
desc 'Builds
|
65
|
+
desc 'Builds Docker images.'
|
66
66
|
command :build do |c|
|
67
|
+
c.desc 'Docker build argument.'
|
67
68
|
c.flag [:a, :arg], required: false, multiple: true
|
68
|
-
|
69
|
+
|
70
|
+
c.desc 'When enabled, ignores missing build arguments.'
|
71
|
+
c.switch [:'ignore-missing-args'], required: false, default: false
|
72
|
+
|
73
|
+
c.desc 'Build only the images associated with the specified identifier(s). '\
|
74
|
+
'Run `kuby images` for a list of all valid identifiers (note that '\
|
75
|
+
'identifiers can be associated with more than one image).'
|
76
|
+
c.flag [:only], required: false, multiple: true
|
77
|
+
|
78
|
+
c.desc 'The directory to use as the Docker build context.'
|
79
|
+
c.flag [:c, :context], required: false
|
80
|
+
|
69
81
|
c.action do |global_options, options, docker_args|
|
70
82
|
build_args = {}.tap do |build_args|
|
71
83
|
(options[:arg] || []).each do |a|
|
@@ -75,30 +87,42 @@ module Kuby
|
|
75
87
|
end
|
76
88
|
end
|
77
89
|
|
78
|
-
tasks.build(
|
90
|
+
tasks.build(
|
91
|
+
build_args, docker_args,
|
92
|
+
only: options[:only],
|
93
|
+
ignore_missing_args: options[:'ignore-missing-args'],
|
94
|
+
context: options[:context]
|
95
|
+
)
|
79
96
|
end
|
80
97
|
end
|
81
98
|
|
82
|
-
desc 'Pushes
|
99
|
+
desc 'Pushes Docker images to their associated registries.'
|
83
100
|
command :push do |c|
|
84
|
-
c.
|
101
|
+
c.desc 'Push only the images associated with the specified identifier(s). '\
|
102
|
+
'Run `kuby images` for a list of all valid identifiers (note that '\
|
103
|
+
'identifiers can be associated with more than one image).'
|
104
|
+
c.flag [:only], required: false, multiple: true
|
85
105
|
c.action do |global_options, options, args|
|
86
|
-
tasks.push(options[:only])
|
106
|
+
tasks.push(only: options[:only])
|
87
107
|
end
|
88
108
|
end
|
89
109
|
|
90
110
|
desc 'Gets your Kubernetes cluster ready to run your Rails app.'
|
91
111
|
command :setup do |c|
|
112
|
+
c.desc 'Run the setup routines for only the specified plugin identifier(s).'
|
113
|
+
c.flag [:only], required: false, multiple: true
|
92
114
|
c.action do |global_options, options, args|
|
93
|
-
tasks.setup
|
115
|
+
tasks.setup(only: options[:only])
|
94
116
|
end
|
95
117
|
end
|
96
118
|
|
97
119
|
desc 'Prints the effective Dockerfiles used to build Docker images.'
|
98
120
|
command :dockerfiles do |c|
|
99
|
-
c.
|
121
|
+
c.desc 'Print Dockerfiles for only the images associated with the specified '\
|
122
|
+
'identifier(s).'
|
123
|
+
c.flag [:only], required: false, multiple: true
|
100
124
|
c.action do |global_options, options, args|
|
101
|
-
tasks.print_dockerfiles(options[:only])
|
125
|
+
tasks.print_dockerfiles(only: options[:only])
|
102
126
|
end
|
103
127
|
end
|
104
128
|
|
@@ -111,7 +135,7 @@ module Kuby
|
|
111
135
|
end
|
112
136
|
end
|
113
137
|
|
114
|
-
desc 'Rolls back to the previous
|
138
|
+
desc 'Rolls back to the previous release.'
|
115
139
|
command :rollback do |c|
|
116
140
|
c.action do |global_options, options, args|
|
117
141
|
tasks.rollback
|
@@ -120,25 +144,37 @@ module Kuby
|
|
120
144
|
|
121
145
|
desc 'Prints the effective Kubernetes resources that will be applied on deploy.'
|
122
146
|
command :resources do |c|
|
147
|
+
c.desc 'Only print resources of the given kind.'
|
123
148
|
c.flag [:K, :kind], required: false
|
149
|
+
|
150
|
+
c.desc 'Only print resources that match the given name.'
|
124
151
|
c.flag [:N, :name], required: false
|
152
|
+
|
125
153
|
c.action do |global_options, options, args|
|
126
154
|
tasks.print_resources(options[:kind], options[:name])
|
127
155
|
end
|
128
156
|
end
|
129
157
|
|
130
|
-
desc 'Prints out the contents of the kubeconfig Kuby is using to communicate
|
158
|
+
desc 'Prints out the contents of the kubeconfig file Kuby is using to communicate '\
|
159
|
+
'with your cluster.'
|
131
160
|
command :kubeconfig do |c|
|
132
161
|
c.action do |global_options, options, args|
|
133
162
|
tasks.print_kubeconfig
|
134
163
|
end
|
135
164
|
end
|
136
165
|
|
166
|
+
desc 'Prints out the URLs to the latest Docker images in the Docker registry.'
|
167
|
+
command :images do |c|
|
168
|
+
c.action do |global_options, options, args|
|
169
|
+
tasks.print_images
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
137
173
|
desc 'Runs an arbitrary kubectl command.'
|
138
174
|
command :kubectl do |c|
|
139
175
|
c.desc 'Prefixes the kubectl command with the namespace associated with '\
|
140
|
-
|
141
|
-
|
176
|
+
'the current environment. For example, if the Kuby env is "production", '\
|
177
|
+
'this option will prefix the kubectl command with "-n myapp-production".'
|
142
178
|
c.switch [:N, :namespaced], default: false
|
143
179
|
c.action do |global_options, options, args|
|
144
180
|
if options[:namespaced]
|
@@ -170,7 +206,7 @@ module Kuby
|
|
170
206
|
rc.desc 'Runs an arbitrary command inside a running Rails pod.'
|
171
207
|
rc.command :exec do |c|
|
172
208
|
c.action do |global_options, options, args|
|
173
|
-
tasks.remote_exec([*args,
|
209
|
+
tasks.remote_exec([*args, *T.unsafe(@rails_options)])
|
174
210
|
end
|
175
211
|
end
|
176
212
|
|
@@ -11,23 +11,15 @@ module Kuby
|
|
11
11
|
dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
|
12
12
|
image_url: String,
|
13
13
|
credentials: Credentials,
|
14
|
+
registry_index_url: T.nilable(String),
|
14
15
|
main_tag: T.nilable(String),
|
15
16
|
alias_tags: T::Array[String]
|
16
17
|
).void
|
17
18
|
}
|
18
|
-
def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
|
19
|
+
def initialize(dockerfile, image_url, credentials, registry_index_url = nil, main_tag = nil, alias_tags = [])
|
19
20
|
super
|
20
21
|
@identifier = "app"
|
21
22
|
end
|
22
|
-
|
23
|
-
sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).returns(AppImage) }
|
24
|
-
def build(build_args = {}, docker_args = [])
|
25
|
-
unless ENV.fetch('RAILS_MASTER_KEY', '').empty?
|
26
|
-
build_args['RAILS_MASTER_KEY'] = T.must(ENV['RAILS_MASTER_KEY'])
|
27
|
-
end
|
28
|
-
|
29
|
-
super(build_args, docker_args)
|
30
|
-
end
|
31
23
|
end
|
32
24
|
end
|
33
25
|
end
|
@@ -29,6 +29,12 @@ module Kuby
|
|
29
29
|
sig { params(without: T::Array[String]).void }
|
30
30
|
attr_writer :without
|
31
31
|
|
32
|
+
sig { returns(T.nilable(String)) }
|
33
|
+
attr_reader :executable
|
34
|
+
|
35
|
+
sig { params(executable: String).void }
|
36
|
+
attr_writer :executable
|
37
|
+
|
32
38
|
sig { params(environment: Environment).void }
|
33
39
|
def initialize(environment)
|
34
40
|
super
|
@@ -37,6 +43,7 @@ module Kuby
|
|
37
43
|
@gemfile = T.let(@gemfile, T.nilable(String))
|
38
44
|
@gemfiles = T.let([], T::Array[String])
|
39
45
|
@without = T.let(@without, T.nilable(T::Array[String]))
|
46
|
+
@executable = T.let(@executable, T.nilable(String))
|
40
47
|
end
|
41
48
|
|
42
49
|
sig { override.params(dockerfile: Dockerfile).void }
|
@@ -61,14 +68,14 @@ module Kuby
|
|
61
68
|
end
|
62
69
|
|
63
70
|
dockerfile.run(
|
64
|
-
'bundle', 'install',
|
71
|
+
executable || 'bundle', 'install',
|
65
72
|
'--jobs', '$(nproc)',
|
66
73
|
'--retry', '3',
|
67
74
|
'--gemfile', gf
|
68
75
|
)
|
69
76
|
|
70
77
|
# generate binstubs and add the bin directory to our path
|
71
|
-
dockerfile.run('bundle', 'binstubs', '--all')
|
78
|
+
dockerfile.run(executable || 'bundle', 'binstubs', '--all')
|
72
79
|
dockerfile.env("PATH=./bin:$PATH")
|
73
80
|
end
|
74
81
|
|
data/lib/kuby/docker/cli.rb
CHANGED
@@ -53,8 +53,15 @@ module Kuby
|
|
53
53
|
config.fetch('auths', {}).keys
|
54
54
|
end
|
55
55
|
|
56
|
-
sig {
|
57
|
-
|
56
|
+
sig {
|
57
|
+
params(
|
58
|
+
image: Image,
|
59
|
+
build_args: T::Hash[T.any(Symbol, String), String],
|
60
|
+
docker_args: T::Array[String],
|
61
|
+
context: T.nilable(String)
|
62
|
+
).void
|
63
|
+
}
|
64
|
+
def build(image, build_args: {}, docker_args: [], context: nil)
|
58
65
|
cmd = [
|
59
66
|
executable, 'build',
|
60
67
|
*image.tags.flat_map { |tag| ['-t', "#{image.image_url}:#{tag}"] },
|
@@ -63,7 +70,7 @@ module Kuby
|
|
63
70
|
end,
|
64
71
|
'-f-',
|
65
72
|
*docker_args,
|
66
|
-
'.'
|
73
|
+
context || '.'
|
67
74
|
]
|
68
75
|
|
69
76
|
open3_w(cmd) do |stdin, _wait_threads|
|
@@ -5,11 +5,36 @@ module Kuby
|
|
5
5
|
class DockerURI
|
6
6
|
extend T::Sig
|
7
7
|
|
8
|
-
DEFAULT_REGISTRY_HOST = T.let('
|
9
|
-
|
8
|
+
DEFAULT_REGISTRY_HOST = T.let('docker.io'.freeze, String)
|
9
|
+
DEFAULT_REGISTRY_INDEX_HOST = T.let('index.docker.io'.freeze, String)
|
10
|
+
DEFAULT_PORT = T.let(443, Integer)
|
10
11
|
|
11
12
|
sig { params(url: String).returns(DockerURI) }
|
12
|
-
def self.
|
13
|
+
def self.parse_uri(url)
|
14
|
+
parse(
|
15
|
+
url,
|
16
|
+
default_host: DEFAULT_REGISTRY_HOST,
|
17
|
+
default_port: DEFAULT_PORT
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(url: String).returns(DockerURI) }
|
22
|
+
def self.parse_index_uri(url)
|
23
|
+
parse(
|
24
|
+
url,
|
25
|
+
default_host: DEFAULT_REGISTRY_INDEX_HOST,
|
26
|
+
default_port: DEFAULT_PORT
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
sig {
|
31
|
+
params(
|
32
|
+
url: String,
|
33
|
+
default_host: T.nilable(String),
|
34
|
+
default_port: T.nilable(Integer)
|
35
|
+
).returns(DockerURI)
|
36
|
+
}
|
37
|
+
def self.parse(url, default_host:, default_port:)
|
13
38
|
if idx = url.index('://')
|
14
39
|
url = url[(idx + 3)..-1] || ''
|
15
40
|
end
|
@@ -17,9 +42,9 @@ module Kuby
|
|
17
42
|
host_port, *path = url.split('/')
|
18
43
|
host, port, *path = if host_port =~ /[.:]/
|
19
44
|
hst, prt = T.must(host_port).split(':')
|
20
|
-
[T.must(hst), prt ||
|
45
|
+
[T.must(hst), prt || default_port, *path]
|
21
46
|
else
|
22
|
-
[
|
47
|
+
[default_host, default_port, host_port, *path]
|
23
48
|
end
|
24
49
|
|
25
50
|
new(host.to_s, port.to_i, (path || []).join('/'))
|
@@ -40,6 +65,11 @@ module Kuby
|
|
40
65
|
@port = port
|
41
66
|
@path = path
|
42
67
|
end
|
68
|
+
|
69
|
+
sig { returns(T::Boolean) }
|
70
|
+
def has_default_port?
|
71
|
+
port == DEFAULT_PORT
|
72
|
+
end
|
43
73
|
end
|
44
74
|
end
|
45
75
|
end
|
data/lib/kuby/docker/image.rb
CHANGED
@@ -11,6 +11,9 @@ module Kuby
|
|
11
11
|
sig { returns(String) }
|
12
12
|
attr_reader :image_url
|
13
13
|
|
14
|
+
sig { returns(T.nilable(String)) }
|
15
|
+
attr_reader :registry_index_url
|
16
|
+
|
14
17
|
sig { returns(Credentials) }
|
15
18
|
attr_reader :credentials
|
16
19
|
|
@@ -25,13 +28,15 @@ module Kuby
|
|
25
28
|
dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
|
26
29
|
image_url: String,
|
27
30
|
credentials: Credentials,
|
31
|
+
registry_index_url: T.nilable(String),
|
28
32
|
main_tag: T.nilable(String),
|
29
33
|
alias_tags: T::Array[String]
|
30
34
|
).void
|
31
35
|
}
|
32
|
-
def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
|
36
|
+
def initialize(dockerfile, image_url, credentials, registry_index_url = nil, main_tag = nil, alias_tags = [])
|
33
37
|
@dockerfile = T.let(dockerfile, T.any(Dockerfile, T.proc.returns(Dockerfile)))
|
34
38
|
@image_url = T.let(image_url, String)
|
39
|
+
@registry_index_url = T.let(registry_index_url, T.nilable(String))
|
35
40
|
@credentials = T.let(credentials, Credentials)
|
36
41
|
@main_tag = T.let(main_tag, T.nilable(String))
|
37
42
|
@alias_tags = T.let(alias_tags, T::Array[String])
|
@@ -39,6 +44,9 @@ module Kuby
|
|
39
44
|
|
40
45
|
@image_host = T.let(@image_host, T.nilable(String))
|
41
46
|
@image_hostname = T.let(@image_hostname, T.nilable(String))
|
47
|
+
@registry_index_host = T.let(@registry_index_host, T.nilable(String))
|
48
|
+
@registry_index_hostname = T.let(@registry_index_hostname, T.nilable(String))
|
49
|
+
@registry_index_uri = T.let(@registry_index_uri, T.nilable(DockerURI))
|
42
50
|
@image_repo = T.let(@image_repo, T.nilable(String))
|
43
51
|
@full_image_uri = T.let(@full_image_uri, T.nilable(DockerURI))
|
44
52
|
@docker_cli = T.let(@docker_cli, T.nilable(Docker::CLI))
|
@@ -73,6 +81,16 @@ module Kuby
|
|
73
81
|
@image_host ||= "#{image_uri.host}:#{image_uri.port}"
|
74
82
|
end
|
75
83
|
|
84
|
+
sig { returns(String) }
|
85
|
+
def registry_index_host
|
86
|
+
@registry_index_host ||= "#{registry_index_uri.host}:#{registry_index_uri.port}"
|
87
|
+
end
|
88
|
+
|
89
|
+
sig { returns(String) }
|
90
|
+
def registry_index_hostname
|
91
|
+
@registry_index_hostname ||= registry_index_uri.host
|
92
|
+
end
|
93
|
+
|
76
94
|
sig { returns(String) }
|
77
95
|
def image_hostname
|
78
96
|
@image_hostname ||= image_uri.host
|
@@ -85,7 +103,12 @@ module Kuby
|
|
85
103
|
|
86
104
|
sig { returns(DockerURI) }
|
87
105
|
def image_uri
|
88
|
-
@full_image_uri ||= DockerURI.
|
106
|
+
@full_image_uri ||= DockerURI.parse_uri(image_url)
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { returns(DockerURI) }
|
110
|
+
def registry_index_uri
|
111
|
+
@registry_index_uri ||= DockerURI.parse_index_uri(registry_index_url || image_url)
|
89
112
|
end
|
90
113
|
|
91
114
|
sig { returns(T::Array[String]) }
|
@@ -93,8 +116,8 @@ module Kuby
|
|
93
116
|
[main_tag, *alias_tags].compact
|
94
117
|
end
|
95
118
|
|
96
|
-
sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).void }
|
97
|
-
def build(build_args = {}, docker_args = [])
|
119
|
+
sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String], context: T.nilable(String)).void }
|
120
|
+
def build(build_args = {}, docker_args = [], context: nil)
|
98
121
|
raise NotImplementedError, 'please use a Docker::Image subclass'
|
99
122
|
end
|
100
123
|
|
@@ -112,7 +135,7 @@ module Kuby
|
|
112
135
|
|
113
136
|
sig { params(main_tag: String, alias_tags: T::Array[String]).returns(Image) }
|
114
137
|
def duplicate_with_tags(main_tag, alias_tags)
|
115
|
-
self.class.new(dockerfile, image_url, credentials, main_tag, alias_tags)
|
138
|
+
self.class.new(dockerfile, image_url, credentials, registry_index_url, main_tag, alias_tags)
|
116
139
|
end
|
117
140
|
end
|
118
141
|
end
|
data/lib/kuby/docker/spec.rb
CHANGED
@@ -13,6 +13,9 @@ module Kuby
|
|
13
13
|
sig { returns(T.nilable(String)) }
|
14
14
|
attr_reader :image_url_str
|
15
15
|
|
16
|
+
sig { returns(T.nilable(String)) }
|
17
|
+
attr_reader :registry_index_url_str
|
18
|
+
|
16
19
|
sig { params(environment: Environment).void }
|
17
20
|
def initialize(environment)
|
18
21
|
@environment = environment
|
@@ -31,6 +34,7 @@ module Kuby
|
|
31
34
|
@layer_stack = T.let(@layer_stack, T.nilable(Kuby::Docker::LayerStack))
|
32
35
|
|
33
36
|
@image_url_str = T.let(@image_url_str, T.nilable(String))
|
37
|
+
@registry_index_url_str = T.let(@registry_index_url_str, T.nilable(String))
|
34
38
|
@image = T.let(@image, T.nilable(Docker::AppImage))
|
35
39
|
end
|
36
40
|
|
@@ -96,6 +100,11 @@ module Kuby
|
|
96
100
|
@image_url_str = url
|
97
101
|
end
|
98
102
|
|
103
|
+
sig { params(url: String).void }
|
104
|
+
def registry_index_url(url)
|
105
|
+
@registry_index_url_str = url
|
106
|
+
end
|
107
|
+
|
99
108
|
sig {
|
100
109
|
params(
|
101
110
|
name: Symbol,
|
@@ -148,7 +157,7 @@ module Kuby
|
|
148
157
|
end
|
149
158
|
|
150
159
|
Docker::AppImage.new(
|
151
|
-
dockerfile, T.must(image_url_str), credentials
|
160
|
+
dockerfile, T.must(image_url_str), credentials, registry_index_url_str
|
152
161
|
)
|
153
162
|
end
|
154
163
|
end
|
@@ -12,11 +12,12 @@ module Kuby
|
|
12
12
|
dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
|
13
13
|
image_url: String,
|
14
14
|
credentials: Credentials,
|
15
|
+
registry_index_url_str: T.nilable(String),
|
15
16
|
main_tag: T.nilable(String),
|
16
17
|
alias_tags: T::Array[String]
|
17
18
|
).void
|
18
19
|
}
|
19
|
-
def initialize(dockerfile, image_url, credentials, main_tag = nil, alias_tags = [])
|
20
|
+
def initialize(dockerfile, image_url, credentials, registry_index_url_str = nil, main_tag = nil, alias_tags = [])
|
20
21
|
@new_version = T.let(@new_version, T.nilable(Image))
|
21
22
|
@current_version = T.let(@current_version, T.nilable(Image))
|
22
23
|
@previous_version = T.let(@previous_version, T.nilable(Image))
|
@@ -30,16 +31,18 @@ module Kuby
|
|
30
31
|
|
31
32
|
sig { returns(Image) }
|
32
33
|
def new_version
|
33
|
-
@new_version ||=
|
34
|
-
TimestampTag.new(Time.now).to_s, [Kuby::Docker::LATEST_TAG]
|
35
|
-
)
|
34
|
+
@new_version ||= create_new_version
|
36
35
|
end
|
37
36
|
|
38
37
|
sig { returns(Image) }
|
39
38
|
def current_version
|
40
|
-
@current_version ||=
|
41
|
-
|
42
|
-
|
39
|
+
@current_version ||= begin
|
40
|
+
duplicate_with_tags(
|
41
|
+
latest_timestamp_tag.to_s, [Kuby::Docker::LATEST_TAG]
|
42
|
+
)
|
43
|
+
rescue MissingTagError
|
44
|
+
create_new_version
|
45
|
+
end
|
43
46
|
end
|
44
47
|
|
45
48
|
sig { params(current_tag: T.nilable(String)).returns(Image) }
|
@@ -73,11 +76,9 @@ module Kuby
|
|
73
76
|
tag
|
74
77
|
end
|
75
78
|
|
76
|
-
sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).void }
|
77
|
-
def build(build_args = {}, docker_args = [])
|
78
|
-
docker_cli.build(new_version, build_args: build_args, docker_args: docker_args)
|
79
|
-
@current_version = new_version
|
80
|
-
@new_version = nil
|
79
|
+
sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String], context: T.nilable(String)).void }
|
80
|
+
def build(build_args = {}, docker_args = [], context: nil)
|
81
|
+
docker_cli.build(new_version, build_args: build_args, docker_args: docker_args, context: context)
|
81
82
|
end
|
82
83
|
|
83
84
|
sig { params(tag: String).void }
|
@@ -87,10 +88,17 @@ module Kuby
|
|
87
88
|
|
88
89
|
private
|
89
90
|
|
91
|
+
sig { returns(Image) }
|
92
|
+
def create_new_version
|
93
|
+
duplicate_with_tags(
|
94
|
+
TimestampTag.new(Time.now).to_s, [Kuby::Docker::LATEST_TAG]
|
95
|
+
)
|
96
|
+
end
|
97
|
+
|
90
98
|
sig { returns(::Docker::Remote::Client) }
|
91
99
|
def remote_client
|
92
100
|
@remote_client ||= ::Docker::Remote::Client.new(
|
93
|
-
|
101
|
+
registry_index_host, image_repo, credentials.username, credentials.password,
|
94
102
|
)
|
95
103
|
end
|
96
104
|
|
@@ -106,7 +114,7 @@ module Kuby
|
|
106
114
|
|
107
115
|
sig { returns(RemoteTags) }
|
108
116
|
def remote
|
109
|
-
@remote ||= RemoteTags.new(remote_client,
|
117
|
+
@remote ||= RemoteTags.new(remote_client, registry_index_host)
|
110
118
|
end
|
111
119
|
end
|
112
120
|
end
|
@@ -7,12 +7,13 @@ module Kuby
|
|
7
7
|
class BareMetalProvider < Provider
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
|
10
|
+
DEFAULT_STORAGE_CLASS = T.let('hostpath'.freeze, String)
|
11
11
|
|
12
12
|
class Config
|
13
13
|
extend ::KubeDSL::ValueFields
|
14
14
|
|
15
15
|
value_fields :kubeconfig
|
16
|
+
value_fields :storage_class
|
16
17
|
end
|
17
18
|
|
18
19
|
sig { returns(Config) }
|
@@ -36,7 +37,7 @@ module Kuby
|
|
36
37
|
|
37
38
|
sig { returns(String) }
|
38
39
|
def storage_class_name
|
39
|
-
|
40
|
+
config.storage_class
|
40
41
|
end
|
41
42
|
|
42
43
|
private
|
@@ -46,6 +47,7 @@ module Kuby
|
|
46
47
|
configure do
|
47
48
|
# default kubeconfig path
|
48
49
|
kubeconfig File.join(ENV['HOME'], '.kube', 'config')
|
50
|
+
storage_class DEFAULT_STORAGE_CLASS
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|
data/lib/kuby/kubernetes/spec.rb
CHANGED
@@ -89,13 +89,23 @@ module Kuby
|
|
89
89
|
@tag = nil
|
90
90
|
end
|
91
91
|
|
92
|
-
def setup
|
93
|
-
|
94
|
-
|
92
|
+
def setup(only: [])
|
93
|
+
plugins = if only.empty?
|
94
|
+
@plugins
|
95
|
+
else
|
96
|
+
@plugins.each_with_object({}) do |(name, plg), memo|
|
97
|
+
memo[name] = plg if only.include?(name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if only.empty?
|
102
|
+
provider.before_setup
|
103
|
+
provider.setup
|
104
|
+
end
|
95
105
|
|
96
|
-
|
97
|
-
|
98
|
-
|
106
|
+
plugins.each { |_, plg| plg.before_setup }
|
107
|
+
plugins.each { |_, plg| plg.setup }
|
108
|
+
plugins.each { |_, plg| plg.after_setup }
|
99
109
|
|
100
110
|
provider.after_setup
|
101
111
|
end
|
data/lib/kuby/plugin.rb
CHANGED
data/lib/kuby/plugin_registry.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# typed: false
|
2
2
|
module Kuby
|
3
3
|
class PluginRegistry
|
4
|
+
include Enumerable
|
5
|
+
|
4
6
|
ANY = 'any'.freeze
|
5
7
|
|
6
8
|
def register(plugin_name, plugin_klass, environment: ANY)
|
@@ -19,6 +21,19 @@ module Kuby
|
|
19
21
|
plugins_by_env[environment] || plugins_by_env[ANY]
|
20
22
|
end
|
21
23
|
|
24
|
+
def each(&block)
|
25
|
+
return to_enum(__method__) unless block
|
26
|
+
|
27
|
+
@plugins.each_pair do |plugin_name, plugins_by_env|
|
28
|
+
plugins_by_env.each_pair do |env, plugin_klass|
|
29
|
+
case env
|
30
|
+
when ANY, Kuby.env
|
31
|
+
yield plugin_name, plugin_klass
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
22
37
|
private
|
23
38
|
|
24
39
|
def plugins
|
@@ -296,7 +296,7 @@ module Kuby
|
|
296
296
|
end
|
297
297
|
|
298
298
|
def image
|
299
|
-
@image ||= RailsApp::AssetsImage.new(docker.image, -> { dockerfile })
|
299
|
+
@image ||= RailsApp::AssetsImage.new(docker.image, -> { dockerfile }, docker.image.registry_index_url)
|
300
300
|
end
|
301
301
|
|
302
302
|
private
|
@@ -324,6 +324,7 @@ module Kuby
|
|
324
324
|
if tag
|
325
325
|
image_name = "#{app_name}-#{tag}"
|
326
326
|
df.from("#{base_image.image_url}:#{tag}", as: image_name)
|
327
|
+
df.arg('RAILS_MASTER_KEY')
|
327
328
|
df.copy("--from=#{prev_image_name} #{RAILS_MOUNT_PATH}", RAILS_MOUNT_PATH)
|
328
329
|
df.run("env RAILS_MASTER_KEY=$RAILS_MASTER_KEY bundle exec rake kuby:rails_app:assets:copy")
|
329
330
|
end
|
@@ -4,16 +4,18 @@ module Kuby
|
|
4
4
|
class AssetsImage < ::Kuby::Docker::Image
|
5
5
|
attr_reader :base_image
|
6
6
|
|
7
|
-
def initialize(base_image, dockerfile, main_tag = nil, alias_tags = [])
|
8
|
-
super(dockerfile, base_image.image_url, base_image.credentials, main_tag, alias_tags)
|
7
|
+
def initialize(base_image, dockerfile, registry_index_url = nil, main_tag = nil, alias_tags = [])
|
8
|
+
super(dockerfile, base_image.image_url, base_image.credentials, registry_index_url, main_tag, alias_tags)
|
9
9
|
@base_image = base_image
|
10
10
|
@identifier = "assets"
|
11
11
|
end
|
12
12
|
|
13
13
|
def new_version
|
14
|
-
# Asset images track the base image, so return the
|
14
|
+
# Asset images track the base image, so return the new version
|
15
15
|
# here. There can be no asset image without a base image.
|
16
|
-
|
16
|
+
@new_version ||= duplicate_with_annotated_tags(
|
17
|
+
base_image.new_version
|
18
|
+
)
|
17
19
|
end
|
18
20
|
|
19
21
|
def current_version
|
@@ -28,8 +30,8 @@ module Kuby
|
|
28
30
|
)
|
29
31
|
end
|
30
32
|
|
31
|
-
def build(build_args = {}, docker_args = [])
|
32
|
-
docker_cli.build(
|
33
|
+
def build(build_args = {}, docker_args = [], context: nil)
|
34
|
+
docker_cli.build(new_version, build_args: build_args, docker_args: docker_args, context: context)
|
33
35
|
end
|
34
36
|
|
35
37
|
def push(tag)
|
@@ -42,6 +44,7 @@ module Kuby
|
|
42
44
|
self.class.new(
|
43
45
|
base_image,
|
44
46
|
dockerfile,
|
47
|
+
registry_index_url,
|
45
48
|
annotate_tag(image.main_tag),
|
46
49
|
image.alias_tags.map { |at| annotate_tag(at) }
|
47
50
|
)
|
@@ -86,16 +86,38 @@ class KubyGenerator < Rails::Generators::Base
|
|
86
86
|
create_file(
|
87
87
|
'.dockerignore',
|
88
88
|
<<~END
|
89
|
-
.
|
90
|
-
|
91
|
-
|
92
|
-
.
|
93
|
-
|
94
|
-
|
95
|
-
log
|
96
|
-
|
97
|
-
|
89
|
+
.git/
|
90
|
+
|
91
|
+
# Ignore bundler config.
|
92
|
+
.bundle
|
93
|
+
|
94
|
+
# Ignore all logfiles and tempfiles.
|
95
|
+
log/*
|
96
|
+
tmp/*
|
97
|
+
!log/.keep
|
98
|
+
!tmp/.keep
|
99
|
+
|
100
|
+
# Ignore pidfiles, but keep the directory.
|
101
|
+
tmp/pids/*
|
102
|
+
!tmp/pids/
|
103
|
+
!tmp/pids/.keep
|
104
|
+
|
105
|
+
# Ignore uploaded files in development.
|
106
|
+
storage/*
|
107
|
+
!storage/.keep
|
108
|
+
|
98
109
|
public/assets
|
110
|
+
**/.byebug_history
|
111
|
+
|
112
|
+
# Ignore master key for decrypting credentials and more.
|
113
|
+
config/master.key
|
114
|
+
|
115
|
+
public/packs
|
116
|
+
public/packs-test
|
117
|
+
node_modules
|
118
|
+
yarn-error.log
|
119
|
+
**/yarn-debug.log*
|
120
|
+
**/.yarn-integrity
|
99
121
|
END
|
100
122
|
)
|
101
123
|
end
|
@@ -209,15 +209,7 @@ module Kuby
|
|
209
209
|
type 'Opaque'
|
210
210
|
|
211
211
|
data do
|
212
|
-
|
213
|
-
add MASTER_KEY_VAR.to_sym, master_key
|
214
|
-
else
|
215
|
-
master_key_path = File.join(spec.root, 'config', 'master.key')
|
216
|
-
|
217
|
-
if File.exist?(master_key_path)
|
218
|
-
add MASTER_KEY_VAR.to_sym, File.read(master_key_path).strip
|
219
|
-
end
|
220
|
-
end
|
212
|
+
add MASTER_KEY_VAR.to_sym, spec.master_key
|
221
213
|
end
|
222
214
|
end
|
223
215
|
|
@@ -434,6 +426,13 @@ module Kuby
|
|
434
426
|
def namespace
|
435
427
|
environment.kubernetes.namespace
|
436
428
|
end
|
429
|
+
|
430
|
+
def master_key
|
431
|
+
@master_key ||= ENV[MASTER_KEY_VAR] || begin
|
432
|
+
master_key_path = File.join(root, 'config', 'master.key')
|
433
|
+
File.read(master_key_path).strip if File.exist?(master_key_path)
|
434
|
+
end
|
435
|
+
end
|
437
436
|
end
|
438
437
|
end
|
439
438
|
end
|
data/lib/kuby/tasks.rb
CHANGED
@@ -9,7 +9,7 @@ module Kuby
|
|
9
9
|
@environment = environment
|
10
10
|
end
|
11
11
|
|
12
|
-
def print_dockerfiles(only
|
12
|
+
def print_dockerfiles(only: nil)
|
13
13
|
kubernetes.docker_images.each do |image|
|
14
14
|
next if only && image.identifier != only
|
15
15
|
|
@@ -24,23 +24,30 @@ module Kuby
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def setup
|
28
|
-
environment.kubernetes.setup
|
27
|
+
def setup(only: [])
|
28
|
+
environment.kubernetes.setup(only: only.map(&:to_sym))
|
29
29
|
end
|
30
30
|
|
31
|
-
def build(build_args = {}, docker_args = [], only
|
31
|
+
def build(build_args = {}, docker_args = [], only: [], ignore_missing_args: false, context: nil)
|
32
|
+
check_platform(docker_args)
|
33
|
+
|
34
|
+
build_args['RAILS_MASTER_KEY'] ||= rails_app.master_key
|
35
|
+
|
36
|
+
check_build_args(build_args) unless ignore_missing_args
|
37
|
+
|
32
38
|
kubernetes.docker_images.each do |image|
|
33
|
-
next
|
39
|
+
next unless only.empty? || only.include?(image.identifier)
|
40
|
+
return unless perform_docker_login_if_necessary(image)
|
34
41
|
|
35
42
|
image = image.new_version
|
36
43
|
Kuby.logger.info("Building image #{image.image_url} with tags #{image.tags.join(', ')}")
|
37
|
-
image.build(build_args, docker_args)
|
44
|
+
image.build(build_args, docker_args, context: context)
|
38
45
|
end
|
39
46
|
end
|
40
47
|
|
41
|
-
def push(only
|
48
|
+
def push(only: [])
|
42
49
|
kubernetes.docker_images.each do |image|
|
43
|
-
next
|
50
|
+
next unless only.empty? || only.include?(image.identifier)
|
44
51
|
|
45
52
|
image = image.current_version
|
46
53
|
Kuby.logger.info("Pushing image #{image.image_url} with tags #{image.tags.join(', ')}")
|
@@ -49,21 +56,7 @@ module Kuby
|
|
49
56
|
end
|
50
57
|
|
51
58
|
def push_image(image)
|
52
|
-
|
53
|
-
Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
|
54
|
-
|
55
|
-
begin
|
56
|
-
image.docker_cli.login(
|
57
|
-
url: image.image_host,
|
58
|
-
username: image.credentials.username,
|
59
|
-
password: image.credentials.password
|
60
|
-
)
|
61
|
-
rescue Kuby::Docker::LoginError => e
|
62
|
-
Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
|
63
|
-
Kuby.logger.fatal(e.message)
|
64
|
-
return
|
65
|
-
end
|
66
|
-
end
|
59
|
+
return unless perform_docker_login_if_necessary(image)
|
67
60
|
|
68
61
|
begin
|
69
62
|
image.tags.each { |tag| image.push(tag) }
|
@@ -104,6 +97,18 @@ module Kuby
|
|
104
97
|
puts File.read(path)
|
105
98
|
end
|
106
99
|
|
100
|
+
def print_images
|
101
|
+
rows = kubernetes.docker_images.flat_map do |image|
|
102
|
+
image = image.current_version
|
103
|
+
|
104
|
+
image.tags.map do |tag|
|
105
|
+
[image.identifier, "#{image.image_url}:#{tag}"]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
puts Kuby::Utils::Table.new(%w(IDENTIFIER URL), rows).to_s
|
110
|
+
end
|
111
|
+
|
107
112
|
def kubectl(*cmd)
|
108
113
|
kubernetes_cli.run_cmd(cmd)
|
109
114
|
end
|
@@ -145,6 +150,106 @@ module Kuby
|
|
145
150
|
|
146
151
|
private
|
147
152
|
|
153
|
+
def check_platform(docker_args)
|
154
|
+
arch, * = RUBY_PLATFORM.split('-')
|
155
|
+
|
156
|
+
if arch != 'x86_64' && !docker_args.include?('--platform')
|
157
|
+
Kuby.logger.fatal(<<~END)
|
158
|
+
Hey there! It looks like your processor isn't x86-compatible.
|
159
|
+
By default, Docker will try to build images that match the
|
160
|
+
current architecture, in this case #{arch}. Most hosting
|
161
|
+
providers run x86 hardware, meaning Docker images built using
|
162
|
+
this computer's architecture might fail to run when deployed
|
163
|
+
to production. You can fix this by running the build command
|
164
|
+
with a special --platform flag, eg:
|
165
|
+
|
166
|
+
bundle exec kuby -e production build -- --platform linux/amd64
|
167
|
+
|
168
|
+
If you meant to build for the current architecture, you can
|
169
|
+
prevent this error by passing the --platform argument for the
|
170
|
+
current architecture, eg. --platform linux/arm64 for ARM, etc.
|
171
|
+
END
|
172
|
+
|
173
|
+
exit 1
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def check_build_args(build_args)
|
178
|
+
required_args = kubernetes.docker_images.flat_map do |image|
|
179
|
+
image.dockerfile.commands.flat_map do |command|
|
180
|
+
case command
|
181
|
+
when Kuby::Docker::Dockerfile::Arg
|
182
|
+
command.args
|
183
|
+
else
|
184
|
+
[]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
required_args.uniq!
|
190
|
+
|
191
|
+
if File.exist?(File.join('config', 'master.key'))
|
192
|
+
required_args.delete('RAILS_MASTER_KEY')
|
193
|
+
end
|
194
|
+
|
195
|
+
missing_args = required_args - build_args.keys
|
196
|
+
|
197
|
+
if missing_args.any?
|
198
|
+
Kuby.logger.fatal(<<~END)
|
199
|
+
The following Docker build arguments are missing: #{missing_args.join(', ')}.
|
200
|
+
Please pass each argument to `kuby build` using the -a or --arg parameter (note
|
201
|
+
that the -a/--arg parameter can be specified multiple times). For example:
|
202
|
+
|
203
|
+
kuby build -a #{missing_args.first}=value ...
|
204
|
+
|
205
|
+
To ignore missing build args, pass the --ignore-missing-args parameter.
|
206
|
+
END
|
207
|
+
|
208
|
+
exit 1
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def perform_docker_login_if_necessary(image)
|
213
|
+
auth_uris = image.docker_cli.auths.map do |url|
|
214
|
+
Kuby::Docker::DockerURI.parse_uri(url)
|
215
|
+
end
|
216
|
+
|
217
|
+
logged_in = image.credentials.username && (
|
218
|
+
auth_uris.any? do |uri|
|
219
|
+
image.image_hostname == uri.host ||
|
220
|
+
image.registry_index_hostname == uri.host
|
221
|
+
end
|
222
|
+
)
|
223
|
+
|
224
|
+
if !logged_in
|
225
|
+
Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
|
226
|
+
|
227
|
+
begin
|
228
|
+
# For some reason, Docker login with a port doesn't work for some
|
229
|
+
# registries (most notably Docker Hub). Since the default is 443 anyway,
|
230
|
+
# it should be fine to omit it.
|
231
|
+
url = if image.image_uri.has_default_port?
|
232
|
+
image.image_hostname # host without port
|
233
|
+
else
|
234
|
+
image.image_host # host with port
|
235
|
+
end
|
236
|
+
|
237
|
+
image.docker_cli.login(
|
238
|
+
url: url,
|
239
|
+
username: image.credentials.username,
|
240
|
+
password: image.credentials.password
|
241
|
+
)
|
242
|
+
rescue Kuby::Docker::LoginError => e
|
243
|
+
Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
|
244
|
+
Kuby.logger.fatal(e.message)
|
245
|
+
|
246
|
+
return false
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
148
253
|
def get_first_pod
|
149
254
|
pods = kubernetes_cli.get_objects(
|
150
255
|
'pods', namespace, match_labels.serialize
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kuby
|
2
|
+
module Utils
|
3
|
+
class Table
|
4
|
+
attr_reader :headers, :rows
|
5
|
+
|
6
|
+
def initialize(headers, rows)
|
7
|
+
@headers = headers
|
8
|
+
@rows = rows
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
[headers, *rows].map { |vals| make_row(vals) }.join("\n")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def make_row(values)
|
18
|
+
columns = values.each_with_index.map do |value, idx|
|
19
|
+
col_width = col_width_at(idx) + 2
|
20
|
+
value.ljust(col_width, ' ')
|
21
|
+
end
|
22
|
+
|
23
|
+
columns.join
|
24
|
+
end
|
25
|
+
|
26
|
+
def col_width_at(idx)
|
27
|
+
col_widths[idx] ||= [headers[idx].size, *rows.map { |r| r[idx].size }].max
|
28
|
+
end
|
29
|
+
|
30
|
+
def col_widths
|
31
|
+
@col_widths ||= {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/kuby/utils.rb
ADDED
data/lib/kuby/version.rb
CHANGED
@@ -19,8 +19,8 @@ describe Kuby::Docker::TimestampedImage do
|
|
19
19
|
subject { image.current_version&.main_tag }
|
20
20
|
|
21
21
|
context 'with no local or remote tags' do
|
22
|
-
it '
|
23
|
-
expect
|
22
|
+
it 'creates a new tag' do
|
23
|
+
expect(subject).to match(/\d{14}/)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kuby-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.17.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cameron Dutro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -285,6 +285,8 @@ files:
|
|
285
285
|
- lib/kuby/railtie.rb
|
286
286
|
- lib/kuby/tasks.rb
|
287
287
|
- lib/kuby/trailing_hash.rb
|
288
|
+
- lib/kuby/utils.rb
|
289
|
+
- lib/kuby/utils/table.rb
|
288
290
|
- lib/kuby/version.rb
|
289
291
|
- spec/docker/spec_spec.rb
|
290
292
|
- spec/docker/timestamp_tag_spec.rb
|