kuby-core 0.12.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4a072588afc0cc4e0cf815c276628b90c192bf24310ed6c4c53089bc467ba278
4
- data.tar.gz: 364b1265256eda0eea1f57469f241a1c78f1bdb2be0d37d8a23b5c6f01f4ff43
3
+ metadata.gz: 1bb1eba2c179a03610f3f478f595c43638e692b41ebad53c7d6292ef5699000a
4
+ data.tar.gz: b9003940ecba163e1f9c08100223d6059920f7f9a2d8ebff0c038e1fd5f335ee
5
5
  SHA512:
6
- metadata.gz: 866438d8c0880d949150d337b01fce94dfab9d0606ab3e2358e4c7cad376bb36cb3ff958ea72188967abf1192216c3b7586ddcfba6d7e0324ab4fbe37a085e49
7
- data.tar.gz: 87e438bd3f4783b1b3d0e7609d773f5eeee91f7b9ae980446c8ca63084bddeb3a525d944750fe13fb36e98d782188cf6bd06c3df5edff742d8a1d5d80aa53966
6
+ metadata.gz: fad183f5c697c6f6a06da8984dff19e0feac91b1996ae79a6605cafb612c77590461f80807ac87854124a97eef31e2999b39a64f997ef4f0fc77564365d81602
7
+ data.tar.gz: 1df3f9153d1595d085511329a5c1110117fa626632438a635f162c38cbe9c48d53df3098b28300408b89021365298b9972d07bdb9abd17b01bbc236e711ed3b8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 0.16.0
2
+ * Allow Bundler executable to be customized.
3
+ - Main use-case is to allow adding in the Prebundler plugin, https://github.com/getkuby/kuby-prebundler.
4
+ * Print error message and exit on missing Docker build args.
5
+ - Ignore with `--ignore-missing-args`.
6
+
7
+ ## 0.15.0
8
+ * Add an extra parameter to the Docker spec for specifying the URL of the Docker registry index.
9
+ - 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.
10
+ * Add config/master.key to the .dockerignore created by the Rails generator.
11
+ - This was an unfortunate oversight, but such are the perils of using pre-1.0 software.
12
+ * Print an error message if building images on non-x86 hardware.
13
+ - 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.
14
+ - We may need to revisit this in the future if ARM processors become more popular.
15
+
16
+ ## 0.14.0
17
+ * Make RAILS_MASTER_KEY available in the assets image (@palkan, #63)
18
+ * Allow specifying custom docker build options via CLI (@palkan, #65)
19
+
20
+ ## 0.13.0
21
+ * Fix handling rails/rake options in remote exec (@palkan, #60)
22
+ * Add `bundler_phase.gemfiles(*paths)` to allow adding additional gemfiles (@palkan, #61)
23
+
1
24
  ## 0.12.0
2
25
  * Fix issue causing volume mount errors when k8s tries to schedule asset pods on multiple nodes (fixes #42).
3
26
  - Persistent volumes can only be mounted on a single physical node.
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  ## Kuby
2
2
 
3
- [![Build Status](https://travis-ci.com/getkuby/kuby-core.svg?branch=master)](https://travis-ci.com/getkuby/kuby-core)
3
+ ![Unit Tests](https://github.com/getkuby/kuby-core/actions/workflows/unit_tests.yml/badge.svg?branch=master)
4
+ ![Integration Tests](https://github.com/getkuby/kuby-core/actions/workflows/integration_tests.yml/badge.svg?branch=master)
4
5
 
5
6
  Deploy your Rails app the easy way.
6
7
 
data/lib/kuby/commands.rb CHANGED
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: false
2
2
 
3
3
  require 'kuby/version'
4
4
  require 'gli'
@@ -25,9 +25,10 @@ module Kuby
25
25
  def run(args)
26
26
  if idx = args.index('rails') || idx = args.index('rake')
27
27
  @rails_options = T.let(@rails_options, T.nilable(T::Array[String]))
28
- @rails_options = args[idx..-1]
28
+ @rails_options = args[(idx + 1)..-1]
29
29
  super(args[0..idx])
30
30
  else
31
+ @rails_options = []
31
32
  super
32
33
  end
33
34
  end
@@ -64,23 +65,30 @@ module Kuby
64
65
  desc 'Builds the Docker image.'
65
66
  command :build do |c|
66
67
  c.flag [:a, :arg], required: false, multiple: true
67
- c.action do |global_options, options, args|
68
+ c.switch [:'ignore-missing-args'], required: false, default: false
69
+ c.flag [:only], required: false
70
+ c.action do |global_options, options, docker_args|
68
71
  build_args = {}.tap do |build_args|
69
72
  (options[:arg] || []).each do |a|
70
- key, value = a.split('=')
73
+ key, value = a.split('=', 2)
71
74
  value = value[1..-2] if value.start_with?('"') || value.start_with?("'")
72
75
  build_args[key] = value
73
76
  end
74
77
  end
75
78
 
76
- tasks.build(build_args)
79
+ tasks.build(
80
+ build_args, docker_args,
81
+ only: options[:only],
82
+ ignore_missing_args: options[:'ignore-missing-args']
83
+ )
77
84
  end
78
85
  end
79
86
 
80
87
  desc 'Pushes the Docker image to the configured registry.'
81
88
  command :push do |c|
89
+ c.flag [:only], required: false
82
90
  c.action do |global_options, options, args|
83
- tasks.push
91
+ tasks.push(only: options[:only])
84
92
  end
85
93
  end
86
94
 
@@ -93,8 +101,9 @@ module Kuby
93
101
 
94
102
  desc 'Prints the effective Dockerfiles used to build Docker images.'
95
103
  command :dockerfiles do |c|
104
+ c.flag [:only], required: false
96
105
  c.action do |global_options, options, args|
97
- tasks.print_dockerfiles
106
+ tasks.print_dockerfiles(only: options[:only])
98
107
  end
99
108
  end
100
109
 
@@ -116,8 +125,10 @@ module Kuby
116
125
 
117
126
  desc 'Prints the effective Kubernetes resources that will be applied on deploy.'
118
127
  command :resources do |c|
128
+ c.flag [:K, :kind], required: false
129
+ c.flag [:N, :name], required: false
119
130
  c.action do |global_options, options, args|
120
- tasks.print_resources
131
+ tasks.print_resources(options[:kind], options[:name])
121
132
  end
122
133
  end
123
134
 
@@ -164,7 +175,7 @@ module Kuby
164
175
  rc.desc 'Runs an arbitrary command inside a running Rails pod.'
165
176
  rc.command :exec do |c|
166
177
  c.action do |global_options, options, args|
167
- tasks.remote_exec(args)
178
+ tasks.remote_exec([*args, *T.unsafe(@rails_options)])
168
179
  end
169
180
  end
170
181
 
@@ -6,13 +6,28 @@ module Kuby
6
6
  class AppImage < ::Kuby::Docker::TimestampedImage
7
7
  extend T::Sig
8
8
 
9
- sig { params(build_args: T::Hash[String, String]).returns(AppImage) }
10
- def build(build_args = {})
9
+ sig {
10
+ params(
11
+ dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
12
+ image_url: String,
13
+ credentials: Credentials,
14
+ registry_index_url: T.nilable(String),
15
+ main_tag: T.nilable(String),
16
+ alias_tags: T::Array[String]
17
+ ).void
18
+ }
19
+ def initialize(dockerfile, image_url, credentials, registry_index_url = nil, main_tag = nil, alias_tags = [])
20
+ super
21
+ @identifier = "app"
22
+ end
23
+
24
+ sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).returns(AppImage) }
25
+ def build(build_args = {}, docker_args = [])
11
26
  unless ENV.fetch('RAILS_MASTER_KEY', '').empty?
12
27
  build_args['RAILS_MASTER_KEY'] = T.must(ENV['RAILS_MASTER_KEY'])
13
28
  end
14
29
 
15
- super(build_args)
30
+ super(build_args, docker_args)
16
31
  end
17
32
  end
18
33
  end
@@ -29,13 +29,21 @@ 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
35
41
 
36
42
  @version = T.let(@version, T.nilable(String))
37
43
  @gemfile = T.let(@gemfile, T.nilable(String))
44
+ @gemfiles = T.let([], T::Array[String])
38
45
  @without = T.let(@without, T.nilable(T::Array[String]))
46
+ @executable = T.let(@executable, T.nilable(String))
39
47
  end
40
48
 
41
49
  sig { override.params(dockerfile: Dockerfile).void }
@@ -51,22 +59,31 @@ module Kuby
51
59
  dockerfile.copy(gf, '.')
52
60
  dockerfile.copy(lf, '.')
53
61
 
62
+ @gemfiles.each do |file|
63
+ dockerfile.copy(file, file)
64
+ end
65
+
54
66
  unless wo.empty?
55
67
  dockerfile.env("BUNDLE_WITHOUT='#{wo.join(' ')}'")
56
68
  end
57
69
 
58
70
  dockerfile.run(
59
- 'bundle', 'install',
71
+ executable || 'bundle', 'install',
60
72
  '--jobs', '$(nproc)',
61
73
  '--retry', '3',
62
74
  '--gemfile', gf
63
75
  )
64
76
 
65
77
  # generate binstubs and add the bin directory to our path
66
- dockerfile.run('bundle', 'binstubs', '--all')
78
+ dockerfile.run(executable || 'bundle', 'binstubs', '--all')
67
79
  dockerfile.env("PATH=./bin:$PATH")
68
80
  end
69
81
 
82
+ sig { params(paths: String).void }
83
+ def gemfiles(*paths)
84
+ @gemfiles.concat(paths)
85
+ end
86
+
70
87
  private
71
88
 
72
89
  sig { returns(String) }
@@ -53,15 +53,17 @@ module Kuby
53
53
  config.fetch('auths', {}).keys
54
54
  end
55
55
 
56
- sig { params(image: Image, build_args: T::Hash[T.any(Symbol, String), String]).void }
57
- def build(image, build_args: {})
56
+ sig { params(image: Image, build_args: T::Hash[T.any(Symbol, String), String], docker_args: T::Array[String]).void }
57
+ def build(image, build_args: {}, docker_args: [])
58
58
  cmd = [
59
59
  executable, 'build',
60
60
  *image.tags.flat_map { |tag| ['-t', "#{image.image_url}:#{tag}"] },
61
61
  *build_args.flat_map do |arg, val|
62
62
  ['--build-arg', Shellwords.shellescape("#{arg}=#{val}")]
63
63
  end,
64
- '-f-', '.'
64
+ '-f-',
65
+ *docker_args,
66
+ '.'
65
67
  ]
66
68
 
67
69
  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('index.docker.io'.freeze, String)
9
- DEFAULT_REGISTRY_PORT = T.let(443, Integer)
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.parse(url)
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 || DEFAULT_REGISTRY_PORT, *path]
45
+ [T.must(hst), prt || default_port, *path]
21
46
  else
22
- [DEFAULT_REGISTRY_HOST, DEFAULT_REGISTRY_PORT, host_port, *path]
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
@@ -5,9 +5,15 @@ module Kuby
5
5
  class Image
6
6
  extend T::Sig
7
7
 
8
+ sig { returns(T.nilable(String)) }
9
+ attr_reader :identifier
10
+
8
11
  sig { returns(String) }
9
12
  attr_reader :image_url
10
13
 
14
+ sig { returns(T.nilable(String)) }
15
+ attr_reader :registry_index_url
16
+
11
17
  sig { returns(Credentials) }
12
18
  attr_reader :credentials
13
19
 
@@ -22,19 +28,25 @@ module Kuby
22
28
  dockerfile: T.any(Dockerfile, T.proc.returns(Dockerfile)),
23
29
  image_url: String,
24
30
  credentials: Credentials,
31
+ registry_index_url: T.nilable(String),
25
32
  main_tag: T.nilable(String),
26
33
  alias_tags: T::Array[String]
27
34
  ).void
28
35
  }
29
- 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 = [])
30
37
  @dockerfile = T.let(dockerfile, T.any(Dockerfile, T.proc.returns(Dockerfile)))
31
38
  @image_url = T.let(image_url, String)
39
+ @registry_index_url = T.let(registry_index_url, T.nilable(String))
32
40
  @credentials = T.let(credentials, Credentials)
33
41
  @main_tag = T.let(main_tag, T.nilable(String))
34
42
  @alias_tags = T.let(alias_tags, T::Array[String])
43
+ @identifier = T.let(@identifier, T.nilable(String))
35
44
 
36
45
  @image_host = T.let(@image_host, T.nilable(String))
37
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))
38
50
  @image_repo = T.let(@image_repo, T.nilable(String))
39
51
  @full_image_uri = T.let(@full_image_uri, T.nilable(DockerURI))
40
52
  @docker_cli = T.let(@docker_cli, T.nilable(Docker::CLI))
@@ -69,6 +81,16 @@ module Kuby
69
81
  @image_host ||= "#{image_uri.host}:#{image_uri.port}"
70
82
  end
71
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
+
72
94
  sig { returns(String) }
73
95
  def image_hostname
74
96
  @image_hostname ||= image_uri.host
@@ -81,7 +103,12 @@ module Kuby
81
103
 
82
104
  sig { returns(DockerURI) }
83
105
  def image_uri
84
- @full_image_uri ||= DockerURI.parse(image_url)
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)
85
112
  end
86
113
 
87
114
  sig { returns(T::Array[String]) }
@@ -89,8 +116,8 @@ module Kuby
89
116
  [main_tag, *alias_tags].compact
90
117
  end
91
118
 
92
- sig { params(build_args: T::Hash[String, String]).void }
93
- def build(build_args = {})
119
+ sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).void }
120
+ def build(build_args = {}, docker_args = [])
94
121
  raise NotImplementedError, 'please use a Docker::Image subclass'
95
122
  end
96
123
 
@@ -108,7 +135,7 @@ module Kuby
108
135
 
109
136
  sig { params(main_tag: String, alias_tags: T::Array[String]).returns(Image) }
110
137
  def duplicate_with_tags(main_tag, alias_tags)
111
- 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)
112
139
  end
113
140
  end
114
141
  end
@@ -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))
@@ -73,9 +74,9 @@ module Kuby
73
74
  tag
74
75
  end
75
76
 
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)
77
+ sig { params(build_args: T::Hash[String, String], docker_args: T::Array[String]).void }
78
+ def build(build_args = {}, docker_args = [])
79
+ docker_cli.build(new_version, build_args: build_args, docker_args: docker_args)
79
80
  @current_version = new_version
80
81
  @new_version = nil
81
82
  end
@@ -90,7 +91,7 @@ module Kuby
90
91
  sig { returns(::Docker::Remote::Client) }
91
92
  def remote_client
92
93
  @remote_client ||= ::Docker::Remote::Client.new(
93
- image_host, image_repo, credentials.username, credentials.password,
94
+ registry_index_host, image_repo, credentials.username, credentials.password,
94
95
  )
95
96
  end
96
97
 
@@ -106,7 +107,7 @@ module Kuby
106
107
 
107
108
  sig { returns(RemoteTags) }
108
109
  def remote
109
- @remote ||= RemoteTags.new(remote_client, image_url)
110
+ @remote ||= RemoteTags.new(remote_client, registry_index_host)
110
111
  end
111
112
  end
112
113
  end
@@ -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
@@ -317,14 +317,16 @@ module Kuby
317
317
  tags.each_cons(2) do |prev_tag, tag|
318
318
  prev_image_name = "#{app_name}-#{prev_tag}"
319
319
  df.from("#{base_image.image_url}:#{prev_tag}", as: prev_image_name)
320
+ df.arg('RAILS_MASTER_KEY')
320
321
  df.run("mkdir -p #{RAILS_MOUNT_PATH}")
321
- df.run("bundle exec rake kuby:rails_app:assets:copy")
322
+ df.run("env RAILS_MASTER_KEY=$RAILS_MASTER_KEY bundle exec rake kuby:rails_app:assets:copy")
322
323
 
323
324
  if tag
324
325
  image_name = "#{app_name}-#{tag}"
325
326
  df.from("#{base_image.image_url}:#{tag}", as: image_name)
327
+ df.arg('RAILS_MASTER_KEY')
326
328
  df.copy("--from=#{prev_image_name} #{RAILS_MOUNT_PATH}", RAILS_MOUNT_PATH)
327
- df.run("bundle exec rake kuby:rails_app:assets:copy")
329
+ df.run("env RAILS_MASTER_KEY=$RAILS_MASTER_KEY bundle exec rake kuby:rails_app:assets:copy")
328
330
  end
329
331
  end
330
332
 
@@ -4,9 +4,10 @@ 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
+ @identifier = "assets"
10
11
  end
11
12
 
12
13
  def new_version
@@ -27,8 +28,12 @@ module Kuby
27
28
  )
28
29
  end
29
30
 
30
- def build(build_args = {})
31
- docker_cli.build(current_version, build_args: build_args)
31
+ def build(build_args = {}, docker_args = [])
32
+ unless ENV.fetch('RAILS_MASTER_KEY', '').empty?
33
+ build_args['RAILS_MASTER_KEY'] = ENV['RAILS_MASTER_KEY']
34
+ end
35
+
36
+ docker_cli.build(current_version, build_args: build_args, docker_args: docker_args)
32
37
  end
33
38
 
34
39
  def push(tag)
@@ -41,6 +46,7 @@ module Kuby
41
46
  self.class.new(
42
47
  base_image,
43
48
  dockerfile,
49
+ registry_index_url,
44
50
  annotate_tag(image.main_tag),
45
51
  image.alias_tags.map { |at| annotate_tag(at) }
46
52
  )
@@ -86,16 +86,38 @@ class KubyGenerator < Rails::Generators::Base
86
86
  create_file(
87
87
  '.dockerignore',
88
88
  <<~END
89
- .bundle/
90
- vendor/bundle
91
- node_modules/
92
- .node_modules/
93
- **/.git*
94
- tmp/
95
- log/
96
- engines/**/log/
97
- engines/**/tmp/
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
data/lib/kuby/tasks.rb CHANGED
@@ -9,10 +9,13 @@ module Kuby
9
9
  @environment = environment
10
10
  end
11
11
 
12
- def print_dockerfiles
12
+ def print_dockerfiles(only: nil)
13
13
  kubernetes.docker_images.each do |image|
14
+ next if only && image.identifier != only
15
+
14
16
  image = image.current_version
15
- Kuby.logger.info("Dockerfile for image #{image.image_url} with tags #{image.tags.join(', ')}")
17
+ identifier = image.identifier ? " ##{image.identifier}" : ""
18
+ Kuby.logger.info("Dockerfile for#{identifier} image #{image.image_url} with tags #{image.tags.join(', ')}")
16
19
  theme = Rouge::Themes::Base16::Solarized.new
17
20
  formatter = Rouge::Formatters::Terminal256.new(theme)
18
21
  lexer = Rouge::Lexers::Docker.new
@@ -25,16 +28,25 @@ module Kuby
25
28
  environment.kubernetes.setup
26
29
  end
27
30
 
28
- def build(build_args = {})
31
+ def build(build_args = {}, docker_args = [], only: nil, ignore_missing_args: false)
32
+ check_platform(docker_args)
33
+ check_build_args(build_args) unless ignore_missing_args
34
+
29
35
  kubernetes.docker_images.each do |image|
36
+ next if only && image.identifier != only
37
+
38
+ return unless perform_docker_login_if_necessary(image)
39
+
30
40
  image = image.new_version
31
41
  Kuby.logger.info("Building image #{image.image_url} with tags #{image.tags.join(', ')}")
32
- image.build(build_args)
42
+ image.build(build_args, docker_args)
33
43
  end
34
44
  end
35
45
 
36
- def push
46
+ def push(only: nil)
37
47
  kubernetes.docker_images.each do |image|
48
+ next if only && image.identifier != only
49
+
38
50
  image = image.current_version
39
51
  Kuby.logger.info("Pushing image #{image.image_url} with tags #{image.tags.join(', ')}")
40
52
  push_image(image)
@@ -42,21 +54,7 @@ module Kuby
42
54
  end
43
55
 
44
56
  def push_image(image)
45
- if image.credentials.username && !image.docker_cli.auths.include?(image.image_host)
46
- Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
47
-
48
- begin
49
- image.docker_cli.login(
50
- url: image.image_host,
51
- username: image.credentials.username,
52
- password: image.credentials.password
53
- )
54
- rescue Kuby::Docker::LoginError => e
55
- Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
56
- Kuby.logger.fatal(e.message)
57
- return
58
- end
59
- end
57
+ return unless perform_docker_login_if_necessary(image)
60
58
 
61
59
  begin
62
60
  image.tags.each { |tag| image.push(tag) }
@@ -77,10 +75,16 @@ module Kuby
77
75
  environment.kubernetes.rollback
78
76
  end
79
77
 
80
- def print_resources
78
+ def print_resources(kind = nil, name_pattern = nil)
81
79
  kubernetes.before_deploy
82
80
 
81
+ name_rxp = Regexp.new(name_pattern) if name_pattern
82
+
83
83
  kubernetes.resources.each do |res|
84
+ next if kind && res.kind_sym.to_s != kind
85
+
86
+ next if name_rxp && !name_rxp.match?(res.metadata.name)
87
+
84
88
  puts res.to_resource.serialize.to_yaml
85
89
  end
86
90
  end
@@ -132,6 +136,106 @@ module Kuby
132
136
 
133
137
  private
134
138
 
139
+ def check_platform(docker_args)
140
+ arch, * = RUBY_PLATFORM.split('-')
141
+
142
+ if arch != 'x86_64' && !docker_args.include?('--platform')
143
+ Kuby.logger.fatal(<<~END)
144
+ Hey there! It looks like your processor isn't x86-compatible.
145
+ By default, Docker will try to build images that match the
146
+ current architecture, in this case #{arch}. Most hosting
147
+ providers run x86 hardware, meaning Docker images built using
148
+ this computer's architecture might fail to run when deployed
149
+ to production. You can fix this by running the build command
150
+ with a special --platform flag, eg:
151
+
152
+ bundle exec kuby -e production build -- --platform linux/amd64
153
+
154
+ If you meant to build for the current architecture, you can
155
+ prevent this error by passing the --platform argument for the
156
+ current architecture, eg. --platform linux/arm64 for ARM, etc.
157
+ END
158
+
159
+ exit 1
160
+ end
161
+ end
162
+
163
+ def check_build_args(build_args)
164
+ required_args = kubernetes.docker_images.flat_map do |image|
165
+ image.dockerfile.commands.flat_map do |command|
166
+ case command
167
+ when Kuby::Docker::Dockerfile::Arg
168
+ command.args
169
+ else
170
+ []
171
+ end
172
+ end
173
+ end
174
+
175
+ required_args.uniq!
176
+
177
+ if File.exist?(File.join('config', 'master.key'))
178
+ required_args.delete('RAILS_MASTER_KEY')
179
+ end
180
+
181
+ missing_args = required_args - build_args.keys
182
+
183
+ if missing_args.any?
184
+ Kuby.logger.fatal(<<~END)
185
+ The following Docker build arguments are missing: #{missing_args.join(', ')}.
186
+ Please pass each argument to `kuby build` using the -a or --arg parameter (note
187
+ that the -a/--arg parameter can be specified multiple times). For example:
188
+
189
+ kuby build -a #{missing_args.first}=value ...
190
+
191
+ To ignore missing build args, pass the --ignore-missing-args parameter.
192
+ END
193
+
194
+ exit 1
195
+ end
196
+ end
197
+
198
+ def perform_docker_login_if_necessary(image)
199
+ auth_uris = image.docker_cli.auths.map do |url|
200
+ Kuby::Docker::DockerURI.parse_uri(url)
201
+ end
202
+
203
+ logged_in = image.credentials.username && (
204
+ auth_uris.any? do |uri|
205
+ image.image_hostname == uri.host ||
206
+ image.registry_index_hostname == uri.host
207
+ end
208
+ )
209
+
210
+ if !logged_in
211
+ Kuby.logger.info("Attempting to log in to registry at #{image.image_host}")
212
+
213
+ begin
214
+ # For some reason, Docker login with a port doesn't work for some
215
+ # registries (most notably Docker Hub). Since the default is 443 anyway,
216
+ # it should be fine to omit it.
217
+ url = if image.image_uri.has_default_port?
218
+ image.image_hostname # host without port
219
+ else
220
+ image.image_host # host with port
221
+ end
222
+
223
+ image.docker_cli.login(
224
+ url: url,
225
+ username: image.credentials.username,
226
+ password: image.credentials.password
227
+ )
228
+ rescue Kuby::Docker::LoginError => e
229
+ Kuby.logger.fatal("Couldn't log in to the registry at #{image.image_host}")
230
+ Kuby.logger.fatal(e.message)
231
+
232
+ return false
233
+ end
234
+ end
235
+
236
+ true
237
+ end
238
+
135
239
  def get_first_pod
136
240
  pods = kubernetes_cli.get_objects(
137
241
  'pods', namespace, match_labels.serialize
data/lib/kuby/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # typed: true
2
2
 
3
3
  module Kuby
4
- VERSION = '0.12.0'.freeze
4
+ VERSION = '0.16.0'.freeze
5
5
  end
@@ -107,6 +107,18 @@ describe Kuby::Docker::Spec do
107
107
  expect(subject).to match(/RUN bundle install .* --gemfile foo\/bar\/Gemfile/)
108
108
  end
109
109
  end
110
+
111
+ context 'when multiple gemfiles are specified' do
112
+ before { spec.bundler_phase.gemfiles('gemfiles/a.gemfile', 'gemfiles/b.gemfile') }
113
+
114
+ it 'uses all gemfiles including the default one' do
115
+ expect(subject).to include("COPY Gemfile .\n")
116
+ expect(subject).to include("COPY Gemfile.lock .\n")
117
+ expect(subject).to include("COPY gemfiles/a.gemfile gemfiles/a.gemfile\n")
118
+ expect(subject).to include("COPY gemfiles/b.gemfile gemfiles/b.gemfile\n")
119
+ expect(subject).to match(/RUN bundle install .* --gemfile Gemfile/)
120
+ end
121
+ end
110
122
  end
111
123
 
112
124
  describe '#package' do
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.12.0
4
+ version: 0.16.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: 2021-06-07 00:00:00.000000000 Z
11
+ date: 2022-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -290,7 +290,6 @@ files:
290
290
  - spec/docker/timestamp_tag_spec.rb
291
291
  - spec/docker/timestamped_image_spec.rb
292
292
  - spec/dummy/Gemfile
293
- - spec/dummy/Gemfile.lock
294
293
  - spec/dummy/README.md
295
294
  - spec/dummy/Rakefile
296
295
  - spec/dummy/app/assets/config/manifest.js
@@ -334,7 +333,6 @@ files:
334
333
  - spec/dummy/config/initializers/mime_types.rb
335
334
  - spec/dummy/config/initializers/wrap_parameters.rb
336
335
  - spec/dummy/config/locales/en.yml
337
- - spec/dummy/config/master.key
338
336
  - spec/dummy/config/puma.rb
339
337
  - spec/dummy/config/routes.rb
340
338
  - spec/dummy/config/spring.rb
@@ -351,7 +349,6 @@ files:
351
349
  - spec/dummy/test/application_system_test_case.rb
352
350
  - spec/dummy/test/channels/application_cable/connection_test.rb
353
351
  - spec/dummy/test/test_helper.rb
354
- - spec/dummy/tmp/cache/bootsnap-load-path-cache
355
352
  - spec/spec_helper.rb
356
353
  - spec/support/docker/fake_cli.rb
357
354
  - spec/support/docker/remote/fake_client.rb
@@ -374,7 +371,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
374
371
  - !ruby/object:Gem::Version
375
372
  version: '0'
376
373
  requirements: []
377
- rubygems_version: 3.1.4
374
+ rubygems_version: 3.2.22
378
375
  signing_key:
379
376
  specification_version: 4
380
377
  summary: Deploy your Rails app onto Kubernetes the easy way.
@@ -1,223 +0,0 @@
1
- GEM
2
- remote: https://rubygems.org/
3
- specs:
4
- actioncable (6.0.3.2)
5
- actionpack (= 6.0.3.2)
6
- nio4r (~> 2.0)
7
- websocket-driver (>= 0.6.1)
8
- actionmailbox (6.0.3.2)
9
- actionpack (= 6.0.3.2)
10
- activejob (= 6.0.3.2)
11
- activerecord (= 6.0.3.2)
12
- activestorage (= 6.0.3.2)
13
- activesupport (= 6.0.3.2)
14
- mail (>= 2.7.1)
15
- actionmailer (6.0.3.2)
16
- actionpack (= 6.0.3.2)
17
- actionview (= 6.0.3.2)
18
- activejob (= 6.0.3.2)
19
- mail (~> 2.5, >= 2.5.4)
20
- rails-dom-testing (~> 2.0)
21
- actionpack (6.0.3.2)
22
- actionview (= 6.0.3.2)
23
- activesupport (= 6.0.3.2)
24
- rack (~> 2.0, >= 2.0.8)
25
- rack-test (>= 0.6.3)
26
- rails-dom-testing (~> 2.0)
27
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
28
- actiontext (6.0.3.2)
29
- actionpack (= 6.0.3.2)
30
- activerecord (= 6.0.3.2)
31
- activestorage (= 6.0.3.2)
32
- activesupport (= 6.0.3.2)
33
- nokogiri (>= 1.8.5)
34
- actionview (6.0.3.2)
35
- activesupport (= 6.0.3.2)
36
- builder (~> 3.1)
37
- erubi (~> 1.4)
38
- rails-dom-testing (~> 2.0)
39
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
40
- activejob (6.0.3.2)
41
- activesupport (= 6.0.3.2)
42
- globalid (>= 0.3.6)
43
- activemodel (6.0.3.2)
44
- activesupport (= 6.0.3.2)
45
- activerecord (6.0.3.2)
46
- activemodel (= 6.0.3.2)
47
- activesupport (= 6.0.3.2)
48
- activestorage (6.0.3.2)
49
- actionpack (= 6.0.3.2)
50
- activejob (= 6.0.3.2)
51
- activerecord (= 6.0.3.2)
52
- marcel (~> 0.3.1)
53
- activesupport (6.0.3.2)
54
- concurrent-ruby (~> 1.0, >= 1.0.2)
55
- i18n (>= 0.7, < 2)
56
- minitest (~> 5.1)
57
- tzinfo (~> 1.1)
58
- zeitwerk (~> 2.2, >= 2.2.2)
59
- addressable (2.7.0)
60
- public_suffix (>= 2.0.2, < 5.0)
61
- bindex (0.8.1)
62
- bootsnap (1.4.8)
63
- msgpack (~> 1.0)
64
- builder (3.2.4)
65
- byebug (11.1.3)
66
- capybara (3.33.0)
67
- addressable
68
- mini_mime (>= 0.1.3)
69
- nokogiri (~> 1.8)
70
- rack (>= 1.6.0)
71
- rack-test (>= 0.6.3)
72
- regexp_parser (~> 1.5)
73
- xpath (~> 3.2)
74
- childprocess (3.0.0)
75
- concurrent-ruby (1.1.7)
76
- crass (1.0.6)
77
- erubi (1.9.0)
78
- ffi (1.13.1)
79
- globalid (0.4.2)
80
- activesupport (>= 4.2.0)
81
- i18n (1.8.5)
82
- concurrent-ruby (~> 1.0)
83
- jbuilder (2.10.0)
84
- activesupport (>= 5.0.0)
85
- listen (3.2.1)
86
- rb-fsevent (~> 0.10, >= 0.10.3)
87
- rb-inotify (~> 0.9, >= 0.9.10)
88
- loofah (2.7.0)
89
- crass (~> 1.0.2)
90
- nokogiri (>= 1.5.9)
91
- mail (2.7.1)
92
- mini_mime (>= 0.1.1)
93
- marcel (0.3.3)
94
- mimemagic (~> 0.3.2)
95
- method_source (1.0.0)
96
- mimemagic (0.3.5)
97
- mini_mime (1.0.2)
98
- mini_portile2 (2.4.0)
99
- minitest (5.14.1)
100
- msgpack (1.3.3)
101
- nio4r (2.5.2)
102
- nokogiri (1.10.10)
103
- mini_portile2 (~> 2.4.0)
104
- public_suffix (4.0.5)
105
- puma (4.3.5)
106
- nio4r (~> 2.0)
107
- rack (2.2.3)
108
- rack-proxy (0.6.5)
109
- rack
110
- rack-test (1.1.0)
111
- rack (>= 1.0, < 3)
112
- rails (6.0.3.2)
113
- actioncable (= 6.0.3.2)
114
- actionmailbox (= 6.0.3.2)
115
- actionmailer (= 6.0.3.2)
116
- actionpack (= 6.0.3.2)
117
- actiontext (= 6.0.3.2)
118
- actionview (= 6.0.3.2)
119
- activejob (= 6.0.3.2)
120
- activemodel (= 6.0.3.2)
121
- activerecord (= 6.0.3.2)
122
- activestorage (= 6.0.3.2)
123
- activesupport (= 6.0.3.2)
124
- bundler (>= 1.3.0)
125
- railties (= 6.0.3.2)
126
- sprockets-rails (>= 2.0.0)
127
- rails-dom-testing (2.0.3)
128
- activesupport (>= 4.2.0)
129
- nokogiri (>= 1.6)
130
- rails-html-sanitizer (1.3.0)
131
- loofah (~> 2.3)
132
- railties (6.0.3.2)
133
- actionpack (= 6.0.3.2)
134
- activesupport (= 6.0.3.2)
135
- method_source
136
- rake (>= 0.8.7)
137
- thor (>= 0.20.3, < 2.0)
138
- rake (13.0.1)
139
- rb-fsevent (0.10.4)
140
- rb-inotify (0.10.1)
141
- ffi (~> 1.0)
142
- regexp_parser (1.7.1)
143
- rubyzip (2.3.0)
144
- sass-rails (6.0.0)
145
- sassc-rails (~> 2.1, >= 2.1.1)
146
- sassc (2.4.0)
147
- ffi (~> 1.9)
148
- sassc-rails (2.1.2)
149
- railties (>= 4.0.0)
150
- sassc (>= 2.0)
151
- sprockets (> 3.0)
152
- sprockets-rails
153
- tilt
154
- selenium-webdriver (3.142.7)
155
- childprocess (>= 0.5, < 4.0)
156
- rubyzip (>= 1.2.2)
157
- spring (2.1.1)
158
- spring-watcher-listen (2.0.1)
159
- listen (>= 2.7, < 4.0)
160
- spring (>= 1.2, < 3.0)
161
- sprockets (4.0.2)
162
- concurrent-ruby (~> 1.0)
163
- rack (> 1, < 3)
164
- sprockets-rails (3.2.1)
165
- actionpack (>= 4.0)
166
- activesupport (>= 4.0)
167
- sprockets (>= 3.0.0)
168
- sqlite3 (1.4.2)
169
- thor (1.0.1)
170
- thread_safe (0.3.6)
171
- tilt (2.0.10)
172
- turbolinks (5.2.1)
173
- turbolinks-source (~> 5.2)
174
- turbolinks-source (5.2.0)
175
- tzinfo (1.2.7)
176
- thread_safe (~> 0.1)
177
- web-console (4.0.4)
178
- actionview (>= 6.0.0)
179
- activemodel (>= 6.0.0)
180
- bindex (>= 0.4.0)
181
- railties (>= 6.0.0)
182
- webdrivers (4.4.1)
183
- nokogiri (~> 1.6)
184
- rubyzip (>= 1.3.0)
185
- selenium-webdriver (>= 3.0, < 4.0)
186
- webpacker (4.3.0)
187
- activesupport (>= 4.2)
188
- rack-proxy (>= 0.6.1)
189
- railties (>= 4.2)
190
- websocket-driver (0.7.3)
191
- websocket-extensions (>= 0.1.0)
192
- websocket-extensions (0.1.5)
193
- xpath (3.2.0)
194
- nokogiri (~> 1.8)
195
- zeitwerk (2.4.0)
196
-
197
- PLATFORMS
198
- ruby
199
-
200
- DEPENDENCIES
201
- bootsnap (>= 1.4.2)
202
- byebug
203
- capybara (>= 2.15)
204
- jbuilder (~> 2.7)
205
- listen (~> 3.2)
206
- puma (~> 4.1)
207
- rails (~> 6.0.3, >= 6.0.3.2)
208
- sass-rails (>= 6)
209
- selenium-webdriver
210
- spring
211
- spring-watcher-listen (~> 2.0.0)
212
- sqlite3 (~> 1.4)
213
- turbolinks (~> 5)
214
- tzinfo-data
215
- web-console (>= 3.3.0)
216
- webdrivers
217
- webpacker (~> 4.0)
218
-
219
- RUBY VERSION
220
- ruby 2.5.8p224
221
-
222
- BUNDLED WITH
223
- 2.1.4
@@ -1 +0,0 @@
1
- e02c0a6ae84e2533f0f5a02962d0ac35